Mercurial > pidgin
changeset 19186:eef82b050c21
merge of 'bbeac90ad01d5059327da9e2504716614a191cab'
and 'f30bfc6fc7aee19d096dd838aad5a784a7371d6c'
author | Kevin Stange <kevin@simguy.net> |
---|---|
date | Sat, 11 Aug 2007 21:08:27 +0000 |
parents | e20619418edf (current diff) cdb0fbe5de7b (diff) |
children | 0ac6c0fbc102 |
files | pidgin/sounds/Makefile.am pidgin/sounds/Makefile.mingw pidgin/sounds/alert.wav pidgin/sounds/login.wav pidgin/sounds/logout.wav pidgin/sounds/receive.wav pidgin/sounds/send.wav |
diffstat | 156 files changed, 2258 insertions(+), 439 deletions(-) [+] |
line wrap: on
line diff
--- a/COPYRIGHT Sat Aug 11 21:08:06 2007 +0000 +++ b/COPYRIGHT Sat Aug 11 21:08:27 2007 +0000 @@ -278,6 +278,7 @@ Joao Luís Marques Pinto Aleksander Piotrowski Julien Pivotto +Eric Polino <aluink@gmail.com> Ari Pollak Robey Pointer Eric Polino
--- a/ChangeLog Sat Aug 11 21:08:06 2007 +0000 +++ b/ChangeLog Sat Aug 11 21:08:27 2007 +0000 @@ -7,6 +7,7 @@ * Server-stored aliases for Yahoo. (John Moody) * Fixed support for Yahoo! doodling. * Bonjour plugin uses native Avahi instead of Howl + * Bonjour plugin supports Buddy Icons Pidgin: * Show current outgoing conversation formatting on the font label on @@ -14,6 +15,9 @@ * Slim new redesign of conversation tabs to maximize number of conversations that can fit in a window + Finch: + * Sound support (Eric Polino) + version 2.1.0 (07/28/2007): libpurple: * Core changes to allow UIs to use second-granularity for scheduling.
--- a/ChangeLog.API Sat Aug 11 21:08:06 2007 +0000 +++ b/ChangeLog.API Sat Aug 11 21:08:27 2007 +0000 @@ -1,5 +1,15 @@ Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul +Version 2.1.1 (x/x/x): + libpurple: + Changed: + * PurpleAccountUiOps.request_authorize's authorize_cb and + deny_cb parameters now correctly have type + PurpleAccountRequestAuthorizationCb rather than GCallback. + (You'll want to change your UI's implementation's signature + to avoid warnings, and then remove some now-redundant casts + back to the proper type.) + version 2.1.0 (7/28/2007): libpurple: Added:
--- a/Makefile.am Sat Aug 11 21:08:06 2007 +0000 +++ b/Makefile.am Sat Aug 11 21:08:27 2007 +0000 @@ -42,7 +42,7 @@ GNT_DIR=finch endif -SUBDIRS = libpurple doc $(GNT_DIR) $(GTK_DIR) m4macros po +SUBDIRS = libpurple doc $(GNT_DIR) $(GTK_DIR) m4macros po share docs: Doxyfile if HAVE_DOXYGEN
--- a/configure.ac Sat Aug 11 21:08:06 2007 +0000 +++ b/configure.ac Sat Aug 11 21:08:27 2007 +0000 @@ -54,9 +54,9 @@ m4_define([gnt_lt_current], [1]) m4_define([gnt_major_version], [2]) -m4_define([gnt_minor_version], [0]) +m4_define([gnt_minor_version], [1]) m4_define([gnt_micro_version], [0]) -m4_define([gnt_version_suffix], []) +m4_define([gnt_version_suffix], [devel]) m4_define([gnt_version], [gnt_major_version.gnt_minor_version.gnt_micro_version]) m4_define([gnt_display_version], gnt_version[]m4_ifdef([gnt_version_suffix],[gnt_version_suffix])) @@ -2086,6 +2086,8 @@ AC_DEFINE(DEBUG, 1, [Define if debugging is enabled.]) fi +AM_CONDITIONAL(PURPLE_AVAILABLE, true) + AC_OUTPUT([Makefile Doxyfile doc/Makefile @@ -2164,7 +2166,6 @@ pidgin/plugins/perl/Makefile pidgin/plugins/perl/common/Makefile.PL pidgin/plugins/ticker/Makefile - pidgin/sounds/Makefile libpurple/example/Makefile libpurple/gconf/Makefile libpurple/purple.pc @@ -2197,6 +2198,8 @@ libpurple/protocols/zephyr/Makefile libpurple/tests/Makefile libpurple/version.h + share/Makefile + share/sounds/Makefile finch/Makefile finch/libgnt/Makefile finch/libgnt/gnt.pc
--- a/finch/Makefile.am Sat Aug 11 21:08:06 2007 +0000 +++ b/finch/Makefile.am Sat Aug 11 21:08:27 2007 +0000 @@ -60,6 +60,7 @@ $(GLIB_LIBS) \ $(LIBXML_LIBS) \ $(GNT_LIBS) \ + $(GSTREAMER_LIBS) \ ./libgnt/libgnt.la \ $(top_builddir)/libpurple/libpurple.la @@ -77,4 +78,5 @@ $(GLIB_CFLAGS) \ $(DBUS_CFLAGS) \ $(LIBXML_CFLAGS) \ + $(GSTREAMER_CFLAGS) \ $(GNT_CFLAGS)
--- a/finch/finch.c Sat Aug 11 21:08:06 2007 +0000 +++ b/finch/finch.c Sat Aug 11 21:08:27 2007 +0000 @@ -55,16 +55,10 @@ purple_debug_set_ui_ops(finch_debug_get_ui_ops()); } -/* XXX: this "leaks" a hashtable on shutdown. I'll let - * the finch guys decide if they want to go through the trouble - * of properly freeing it, since their quit function doesn't - * live in this file */ - static GHashTable *ui_info = NULL; - static GHashTable *finch_ui_get_info() { - if(NULL == ui_info) { + if (ui_info == NULL) { ui_info = g_hash_table_new(g_str_hash, g_str_equal); g_hash_table_insert(ui_info, "name", (char*)_("Finch")); @@ -74,12 +68,20 @@ return ui_info; } +static void +finch_quit(void) +{ + gnt_ui_uninit(); + if (ui_info) + g_hash_table_destroy(ui_info); +} + static PurpleCoreUiOps core_ops = { finch_prefs_init, debug_init, gnt_ui_init, - gnt_ui_uninit, + finch_quit, finch_ui_get_info, /* padding */ @@ -396,7 +398,17 @@ return 1; } -int main(int argc, char **argv) +static gboolean gnt_start(int *argc, char ***argv) +{ + /* Initialize the libpurple stuff */ + if (!init_libpurple(*argc, *argv)) + return FALSE; + + purple_blist_show(); + return TRUE; +} + +int main(int argc, char *argv[]) { signal(SIGPIPE, SIG_IGN); @@ -405,11 +417,10 @@ g_set_application_name(_("Finch")); #endif - /* Initialize the libpurple stuff */ - if (!init_libpurple(argc, argv)) - return 0; - - purple_blist_show(); + gnt_init(); + + gnt_start(&argc, &argv); + gnt_main(); #ifdef STANDALONE
--- a/finch/finch.h Sat Aug 11 21:08:06 2007 +0000 +++ b/finch/finch.h Sat Aug 11 21:08:27 2007 +0000 @@ -27,3 +27,4 @@ #define FINCH_UI "gnt-purple" +#define FINCH_PREFS_ROOT "/finch"
--- a/finch/gntaccount.c Sat Aug 11 21:08:06 2007 +0000 +++ b/finch/gntaccount.c Sat Aug 11 21:08:27 2007 +0000 @@ -912,9 +912,15 @@ } static void * -finch_request_authorize(PurpleAccount *account, const char *remote_user, - const char *id, const char *alias, const char *message, gboolean on_list, - GCallback auth_cb, GCallback deny_cb, void *user_data) +finch_request_authorize(PurpleAccount *account, + const char *remote_user, + const char *id, + const char *alias, + const char *message, + gboolean on_list, + PurpleAccountRequestAuthorizationCb auth_cb, + PurpleAccountRequestAuthorizationCb deny_cb, + void *user_data) { char *buffer; PurpleConnection *gc; @@ -941,8 +947,8 @@ GList *iter; auth_and_add *aa = g_new(auth_and_add, 1); - aa->auth_cb = (PurpleAccountRequestAuthorizationCb)auth_cb; - aa->deny_cb = (PurpleAccountRequestAuthorizationCb)deny_cb; + aa->auth_cb = auth_cb; + aa->deny_cb = deny_cb; aa->data = user_data; aa->username = g_strdup(remote_user); aa->alias = g_strdup(alias);
--- a/finch/gntconv.h Sat Aug 11 21:08:06 2007 +0000 +++ b/finch/gntconv.h Sat Aug 11 21:08:27 2007 +0000 @@ -30,6 +30,9 @@ #include "conversation.h" +/* Grabs the conv out of a PurpleConverstation */ +#define FINCH_CONV(conv) ((FinchConv *)(conv)->ui_data) + /*************************************************************************** * @name GNT Conversations API ***************************************************************************/
--- a/finch/gntsound.c Sat Aug 11 21:08:06 2007 +0000 +++ b/finch/gntsound.c Sat Aug 11 21:08:27 2007 +0000 @@ -23,28 +23,1052 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "internal.h" +#include "finch.h" + +#ifdef _WIN32 +#include <windows.h> +#include <mmsystem.h> +#endif + +#ifdef USE_GSTREAMER +#include <gst/gst.h> +#endif /* USE_GSTREAMER */ + +#include "debug.h" +#include "notify.h" +#include "prefs.h" +#include "sound.h" +#include "util.h" + +#include "gntbox.h" +#include "gntwindow.h" +#include "gntcombobox.h" +#include "gntlabel.h" +#include "gntconv.h" #include "gntsound.h" +#include "gntwidget.h" +#include "gntentry.h" +#include "gntcheckbox.h" +#include "gntline.h" +#include "gntslider.h" +#include "gnttree.h" +#include "gntfilesel.h" + +typedef struct { + PurpleSoundEventID id; + char *label; + char *pref; + char *def; + char *file; +} FinchSoundEvent; + +typedef struct { + GntWidget *method; + GntWidget *command; + GntWidget *conv_focus; + GntWidget *while_status; + GntWidget *volume; + GntWidget *events; + GntWidget *window; + GntWidget *selector; + + GntWidget *profiles; + GntWidget *new_profile; + gchar * original_profile; +} SoundPrefDialog; + +#define DEFAULT_PROFILE "default" + +static SoundPrefDialog *pref_dialog; + +#define PLAY_SOUND_TIMEOUT 15000 + +static guint mute_login_sounds_timeout = 0; +static gboolean mute_login_sounds = FALSE; + +#ifdef USE_GSTREAMER +static gboolean gst_init_failed; +#endif /* USE_GSTREAMER */ + +static FinchSoundEvent sounds[PURPLE_NUM_SOUNDS] = { + {PURPLE_SOUND_BUDDY_ARRIVE, N_("Buddy logs in"), "login", "login.wav", NULL}, + {PURPLE_SOUND_BUDDY_LEAVE, N_("Buddy logs out"), "logout", "logout.wav", NULL}, + {PURPLE_SOUND_RECEIVE, N_("Message received"), "im_recv", "receive.wav", NULL}, + {PURPLE_SOUND_FIRST_RECEIVE, N_("Message received begins conversation"), "first_im_recv", "receive.wav", NULL}, + {PURPLE_SOUND_SEND, N_("Message sent"), "send_im", "send.wav", NULL}, + {PURPLE_SOUND_CHAT_JOIN, N_("Person enters chat"), "join_chat", "login.wav", NULL}, + {PURPLE_SOUND_CHAT_LEAVE, N_("Person leaves chat"), "left_chat", "logout.wav", NULL}, + {PURPLE_SOUND_CHAT_YOU_SAY, N_("You talk in chat"), "send_chat_msg", "send.wav", NULL}, + {PURPLE_SOUND_CHAT_SAY, N_("Others talk in chat"), "chat_msg_recv", "receive.wav", NULL}, + {PURPLE_SOUND_POUNCE_DEFAULT, NULL, "pounce_default", "alert.wav", NULL}, + {PURPLE_SOUND_CHAT_NICK, N_("Someone says your screen name in chat"), "nick_said", "alert.wav", NULL} +}; + +const char * +finch_sound_get_active_profile() +{ + return purple_prefs_get_string(FINCH_PREFS_ROOT "/sound/actprofile"); +} -const char *finch_sound_get_active_profile(void) +/* This method creates a pref name based on the current active profile. + * So if "Home" is the current active profile the pref name + * [FINCH_PREFS_ROOT "/sound/profiles/Home/$NAME"] is created. + */ +static gchar * +make_pref(const char *name) +{ + static char pref_string[512]; + g_snprintf(pref_string, sizeof(pref_string), + FINCH_PREFS_ROOT "/sound/profiles/%s%s", finch_sound_get_active_profile(), name); + return pref_string; +} + + +static gboolean +unmute_login_sounds_cb(gpointer data) +{ + mute_login_sounds = FALSE; + mute_login_sounds_timeout = 0; + return FALSE; +} + +static gboolean +chat_nick_matches_name(PurpleConversation *conv, const char *aname) { - return NULL; + PurpleConvChat *chat = NULL; + char *nick = NULL; + char *name = NULL; + gboolean ret = FALSE; + chat = purple_conversation_get_chat_data(conv); + + if (chat == NULL) + return ret; + + nick = g_strdup(purple_normalize(conv->account, chat->nick)); + name = g_strdup(purple_normalize(conv->account, aname)); + + if (g_utf8_collate(nick, name) == 0) + ret = TRUE; + + g_free(nick); + g_free(name); + + return ret; +} + +/* + * play a sound event for a conversation, honoring make_sound flag + * of conversation and checking for focus if conv_focus pref is set + */ +static void +play_conv_event(PurpleConversation *conv, PurpleSoundEventID event) +{ + /* If we should not play the sound for some reason, then exit early */ + if (conv != NULL) + { + FinchConv *gntconv; + gboolean has_focus; + + gntconv = FINCH_CONV(conv); + + has_focus = purple_conversation_has_focus(conv); + + if (has_focus && !purple_prefs_get_bool(make_pref("/conv_focus"))) + { + return; + } + } + + purple_sound_play_event(event, conv ? purple_conversation_get_account(conv) : NULL); +} + +static void +buddy_state_cb(PurpleBuddy *buddy, PurpleSoundEventID event) +{ + purple_sound_play_event(event, purple_buddy_get_account(buddy)); +} + +static void +im_msg_received_cb(PurpleAccount *account, char *sender, + char *message, PurpleConversation *conv, + PurpleMessageFlags flags, PurpleSoundEventID event) +{ + if (flags & PURPLE_MESSAGE_DELAYED) + return; + + if (conv == NULL) { + purple_sound_play_event(PURPLE_SOUND_FIRST_RECEIVE, account); + } else { + play_conv_event(conv, event); + } } -void finch_sound_set_active_profile(const char *name) +static void +im_msg_sent_cb(PurpleAccount *account, const char *receiver, + const char *message, PurpleSoundEventID event) +{ + PurpleConversation *conv = purple_find_conversation_with_account( + PURPLE_CONV_TYPE_ANY, receiver, account); + play_conv_event(conv, event); +} + +static void +chat_buddy_join_cb(PurpleConversation *conv, const char *name, + PurpleConvChatBuddyFlags flags, gboolean new_arrival, + PurpleSoundEventID event) +{ + if (new_arrival && !chat_nick_matches_name(conv, name)) + play_conv_event(conv, event); +} + +static void +chat_buddy_left_cb(PurpleConversation *conv, const char *name, + const char *reason, PurpleSoundEventID event) +{ + if (!chat_nick_matches_name(conv, name)) + play_conv_event(conv, event); +} + +static void +chat_msg_sent_cb(PurpleAccount *account, const char *message, + int id, PurpleSoundEventID event) +{ + PurpleConnection *conn = purple_account_get_connection(account); + PurpleConversation *conv = NULL; + + if (conn!=NULL) + conv = purple_find_chat(conn, id); + + play_conv_event(conv, event); +} + +static void +chat_msg_received_cb(PurpleAccount *account, char *sender, + char *message, PurpleConversation *conv, + PurpleMessageFlags flags, PurpleSoundEventID event) +{ + PurpleConvChat *chat; + + if (flags & PURPLE_MESSAGE_DELAYED) + return; + + chat = purple_conversation_get_chat_data(conv); + g_return_if_fail(chat != NULL); + + if (purple_conv_chat_is_user_ignored(chat, sender)) + return; + + if (chat_nick_matches_name(conv, sender)) + return; + + if (flags & PURPLE_MESSAGE_NICK || purple_utf8_has_word(message, chat->nick)) + play_conv_event(conv, PURPLE_SOUND_CHAT_NICK); + else + 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. + */ +static void +account_signon_cb(PurpleConnection *gc, gpointer data) +{ + if (mute_login_sounds_timeout != 0) + g_source_remove(mute_login_sounds_timeout); + mute_login_sounds = TRUE; + mute_login_sounds_timeout = purple_timeout_add_seconds(10, unmute_login_sounds_cb, NULL); +} + +static void * +finch_sound_get_handle() +{ + static int handle; + + return &handle; +} + + +/* This gets called when the active profile changes */ +static void +initialize_profile(const char *name, PurplePrefType type, gconstpointer val, gpointer null) { + if (purple_prefs_exists(make_pref(""))) + return; + + purple_prefs_add_none(make_pref("")); + purple_prefs_add_none(make_pref("/enabled")); + purple_prefs_add_none(make_pref("/file")); + purple_prefs_add_bool(make_pref("/enabled/login"), TRUE); + purple_prefs_add_path(make_pref("/file/login"), ""); + purple_prefs_add_bool(make_pref("/enabled/logout"), TRUE); + purple_prefs_add_path(make_pref("/file/logout"), ""); + purple_prefs_add_bool(make_pref("/enabled/im_recv"), TRUE); + purple_prefs_add_path(make_pref("/file/im_recv"), ""); + purple_prefs_add_bool(make_pref("/enabled/first_im_recv"), FALSE); + purple_prefs_add_path(make_pref("/file/first_im_recv"), ""); + purple_prefs_add_bool(make_pref("/enabled/send_im"), TRUE); + purple_prefs_add_path(make_pref("/file/send_im"), ""); + purple_prefs_add_bool(make_pref("/enabled/join_chat"), FALSE); + purple_prefs_add_path(make_pref("/file/join_chat"), ""); + purple_prefs_add_bool(make_pref("/enabled/left_chat"), FALSE); + purple_prefs_add_path(make_pref("/file/left_chat"), ""); + purple_prefs_add_bool(make_pref("/enabled/send_chat_msg"), FALSE); + purple_prefs_add_path(make_pref("/file/send_chat_msg"), ""); + purple_prefs_add_bool(make_pref("/enabled/chat_msg_recv"), FALSE); + purple_prefs_add_path(make_pref("/file/chat_msg_recv"), ""); + purple_prefs_add_bool(make_pref("/enabled/nick_said"), FALSE); + purple_prefs_add_path(make_pref("/file/nick_said"), ""); + purple_prefs_add_bool(make_pref("/enabled/pounce_default"), TRUE); + purple_prefs_add_path(make_pref("/file/pounce_default"), ""); + purple_prefs_add_bool(make_pref("/conv_focus"), TRUE); + purple_prefs_add_bool(make_pref("/mute"), FALSE); + purple_prefs_add_path(make_pref("/command"), ""); + purple_prefs_add_string(make_pref("/method"), "automatic"); + purple_prefs_add_int(make_pref("/volume"), 50); +} + +static void +finch_sound_init(void) +{ + void *gnt_sound_handle = finch_sound_get_handle(); + void *blist_handle = purple_blist_get_handle(); + void *conv_handle = purple_conversations_get_handle(); +#ifdef USE_GSTREAMER + GError *error = NULL; +#endif + + purple_signal_connect(purple_connections_get_handle(), "signed-on", + gnt_sound_handle, PURPLE_CALLBACK(account_signon_cb), + NULL); + + purple_prefs_add_none(FINCH_PREFS_ROOT "/sound"); + purple_prefs_add_string(FINCH_PREFS_ROOT "/sound/actprofile", DEFAULT_PROFILE); + purple_prefs_add_none(FINCH_PREFS_ROOT "/sound/profiles"); + + purple_prefs_connect_callback(gnt_sound_handle, FINCH_PREFS_ROOT "/sound/actprofile", initialize_profile, NULL); + purple_prefs_trigger_callback(FINCH_PREFS_ROOT "/sound/actprofile"); + + +#ifdef USE_GSTREAMER + purple_debug_info("sound", "Initializing sound output drivers.\n"); + if ((gst_init_failed = !gst_init_check(NULL, NULL, &error))) { + purple_notify_error(NULL, _("GStreamer Failure"), + _("GStreamer failed to initialize."), + error ? error->message : ""); + if (error) { + g_error_free(error); + error = NULL; + } + } +#endif /* USE_GSTREAMER */ + + purple_signal_connect(blist_handle, "buddy-signed-on", + gnt_sound_handle, PURPLE_CALLBACK(buddy_state_cb), + GINT_TO_POINTER(PURPLE_SOUND_BUDDY_ARRIVE)); + purple_signal_connect(blist_handle, "buddy-signed-off", + gnt_sound_handle, PURPLE_CALLBACK(buddy_state_cb), + GINT_TO_POINTER(PURPLE_SOUND_BUDDY_LEAVE)); + purple_signal_connect(conv_handle, "received-im-msg", + gnt_sound_handle, PURPLE_CALLBACK(im_msg_received_cb), + GINT_TO_POINTER(PURPLE_SOUND_RECEIVE)); + purple_signal_connect(conv_handle, "sent-im-msg", + gnt_sound_handle, PURPLE_CALLBACK(im_msg_sent_cb), + GINT_TO_POINTER(PURPLE_SOUND_SEND)); + purple_signal_connect(conv_handle, "chat-buddy-joined", + gnt_sound_handle, PURPLE_CALLBACK(chat_buddy_join_cb), + GINT_TO_POINTER(PURPLE_SOUND_CHAT_JOIN)); + purple_signal_connect(conv_handle, "chat-buddy-left", + gnt_sound_handle, PURPLE_CALLBACK(chat_buddy_left_cb), + GINT_TO_POINTER(PURPLE_SOUND_CHAT_LEAVE)); + purple_signal_connect(conv_handle, "sent-chat-msg", + gnt_sound_handle, PURPLE_CALLBACK(chat_msg_sent_cb), + GINT_TO_POINTER(PURPLE_SOUND_CHAT_YOU_SAY)); + purple_signal_connect(conv_handle, "received-chat-msg", + gnt_sound_handle, PURPLE_CALLBACK(chat_msg_received_cb), + GINT_TO_POINTER(PURPLE_SOUND_CHAT_SAY)); +} + +static void +finch_sound_uninit(void) +{ +#ifdef USE_GSTREAMER + if (!gst_init_failed) + gst_deinit(); +#endif + + purple_signals_disconnect_by_handle(finch_sound_get_handle()); } -GList *finch_sound_get_profiles(void) +#ifdef USE_GSTREAMER +static gboolean +bus_call (GstBus *bus, GstMessage *msg, gpointer data) +{ + GstElement *play = data; + GError *err = NULL; + + switch (GST_MESSAGE_TYPE (msg)) { + case GST_MESSAGE_EOS: + gst_element_set_state(play, GST_STATE_NULL); + gst_object_unref(GST_OBJECT(play)); + break; + case GST_MESSAGE_ERROR: + gst_message_parse_error(msg, &err, NULL); + purple_debug_error("gstreamer", "%s\n", err->message); + g_error_free(err); + break; + case GST_MESSAGE_WARNING: + gst_message_parse_warning(msg, &err, NULL); + purple_debug_warning("gstreamer", "%s\n", err->message); + g_error_free(err); + break; + default: + break; + } + return TRUE; +} +#endif + +static void +finch_sound_play_file(const char *filename) +{ + const char *method; +#ifdef USE_GSTREAMER + float volume; + char *uri; + GstElement *sink = NULL; + GstElement *play = NULL; + GstBus *bus = NULL; +#endif + if (purple_prefs_get_bool(make_pref("/mute"))) + return; + + method = purple_prefs_get_string(make_pref("/method")); + + if (!strcmp(method, "none")) { + return; + } else if (!strcmp(method, "beep")) { + beep(); + return; + } + + if (!g_file_test(filename, G_FILE_TEST_EXISTS)) { + purple_debug_error("gntsound", "sound file (%s) does not exist.\n", filename); + return; + } + +#ifndef _WIN32 + if (!strcmp(method, "custom")) { + const char *sound_cmd; + char *command; + char *esc_filename; + GError *error = NULL; + + sound_cmd = purple_prefs_get_path(make_pref("/command")); + + if (!sound_cmd || *sound_cmd == '\0') { + purple_debug_error("gntsound", + "'Command' sound method has been chosen, " + "but no command has been set."); + return; + } + + esc_filename = g_shell_quote(filename); + + if (strstr(sound_cmd, "%s")) + command = purple_strreplace(sound_cmd, "%s", esc_filename); + else + command = g_strdup_printf("%s %s", sound_cmd, esc_filename); + + if (!g_spawn_command_line_async(command, &error)) { + purple_debug_error("gntsound", "sound command could not be launched: %s\n", error->message); + g_error_free(error); + } + + g_free(esc_filename); + g_free(command); + return; + } +#ifdef USE_GSTREAMER + if (gst_init_failed) /* Perhaps do beep instead? */ + return; + volume = (float)(CLAMP(purple_prefs_get_int(make_pref("/volume")), 0, 100)) / 50; + if (!strcmp(method, "automatic")) { + if (purple_running_gnome()) { + sink = gst_element_factory_make("gconfaudiosink", "sink"); + } + if (!sink) + sink = gst_element_factory_make("autoaudiosink", "sink"); + if (!sink) { + purple_debug_error("sound", "Unable to create GStreamer audiosink.\n"); + return; + } + } else if (!strcmp(method, "esd")) { + sink = gst_element_factory_make("esdsink", "sink"); + if (!sink) { + purple_debug_error("sound", "Unable to create GStreamer audiosink.\n"); + return; + } + } else if (!strcmp(method, "alsa")) { + sink = gst_element_factory_make("alsasink", "sink"); + if (!sink) { + purple_debug_error("sound", "Unable to create GStreamer audiosink.\n"); + return; + } + } else { + purple_debug_error("sound", "Unknown sound method '%s'\n", method); + return; + } + + play = gst_element_factory_make("playbin", "play"); + + if (play == NULL) { + return; + } + + uri = g_strdup_printf("file://%s", filename); + + g_object_set(G_OBJECT(play), "uri", uri, + "volume", volume, + "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); + + gst_object_unref(bus); + g_free(uri); + +#else /* USE_GSTREAMER */ + beep(); + return; +#endif /* USE_GSTREAMER */ +#else /* _WIN32 */ + purple_debug_info("sound", "Playing %s\n", filename); + + if (G_WIN32_HAVE_WIDECHAR_API ()) { + wchar_t *wc_filename = g_utf8_to_utf16(filename, + -1, NULL, NULL, NULL); + if (!PlaySoundW(wc_filename, NULL, SND_ASYNC | SND_FILENAME)) + purple_debug(PURPLE_DEBUG_ERROR, "sound", "Error playing sound.\n"); + g_free(wc_filename); + } else { + char *l_filename = g_locale_from_utf8(filename, + -1, NULL, NULL, NULL); + if (!PlaySoundA(l_filename, NULL, SND_ASYNC | SND_FILENAME)) + purple_debug(PURPLE_DEBUG_ERROR, "sound", "Error playing sound.\n"); + g_free(l_filename); + } +#endif /* _WIN32 */ +} + +static void +finch_sound_play_event(PurpleSoundEventID event) { - return NULL; + char *enable_pref; + char *file_pref; + if ((event == PURPLE_SOUND_BUDDY_ARRIVE) && mute_login_sounds) + return; + + if (event >= PURPLE_NUM_SOUNDS) { + purple_debug_error("sound", "got request for unknown sound: %d\n", event); + return; + } + + enable_pref = g_strdup_printf(FINCH_PREFS_ROOT "/sound/profiles/%s/enabled/%s", + finch_sound_get_active_profile(), + sounds[event].pref); + file_pref = g_strdup_printf(FINCH_PREFS_ROOT "/sound/profiles/%s/file/%s", finch_sound_get_active_profile(), 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)); + if (!filename || !strlen(filename)) { + g_free(filename); + /* XXX Consider creating a constant for "sounds/purple" to be shared with Pidgin */ + filename = g_build_filename(DATADIR, "sounds", "purple", sounds[event].def, NULL); + } + + purple_sound_play_file(filename, NULL); + g_free(filename); + } + + g_free(enable_pref); + g_free(file_pref); +} + +GList * +finch_sound_get_profiles() +{ + GList *list = NULL, *iter; + iter = purple_prefs_get_children_names(FINCH_PREFS_ROOT "/sound/profiles"); + while (iter) { + list = g_list_append(list, g_strdup(strrchr(iter->data, '/') + 1)); + g_free(iter->data); + iter = g_list_delete_link(iter, iter); + } + return list; +} + +/* This will also create it if it doesn't exist */ +void +finch_sound_set_active_profile(const char *name) +{ + purple_prefs_set_string(FINCH_PREFS_ROOT "/sound/actprofile", name); +} + +static gboolean +finch_sound_profile_exists(const char *name) +{ + gchar * tmp; + gboolean ret = purple_prefs_exists(tmp = g_strdup_printf(FINCH_PREFS_ROOT "/sound/profiles/%s", name)); + g_free(tmp); + return ret; +} + +static void +save_cb(GntWidget *button, gpointer win) +{ + GList * itr; + + purple_prefs_set_string(make_pref("/method"), gnt_combo_box_get_selected_data(GNT_COMBO_BOX(pref_dialog->method))); + purple_prefs_set_path(make_pref("/command"), gnt_entry_get_text(GNT_ENTRY(pref_dialog->command))); + purple_prefs_set_bool(make_pref("/conv_focus"), gnt_check_box_get_checked(GNT_CHECK_BOX(pref_dialog->conv_focus))); + purple_prefs_set_int("/purple/sound/while_status", GPOINTER_TO_INT(gnt_combo_box_get_selected_data(GNT_COMBO_BOX(pref_dialog->while_status)))); + purple_prefs_set_int(make_pref("/volume"), gnt_slider_get_value(GNT_SLIDER(pref_dialog->volume))); + + for (itr = gnt_tree_get_rows(GNT_TREE(pref_dialog->events)); itr; itr = itr->next) { + FinchSoundEvent * event = &sounds[GPOINTER_TO_INT(itr->data)]; + char * filepref = g_strdup_printf(FINCH_PREFS_ROOT "/sound/profiles/%s/file/%s", finch_sound_get_active_profile(), event->pref); + char * boolpref = g_strdup_printf(FINCH_PREFS_ROOT "/sound/profiles/%s/enabled/%s", finch_sound_get_active_profile(), event->pref); + purple_prefs_set_bool(boolpref, gnt_tree_get_choice(GNT_TREE(pref_dialog->events), itr->data)); + purple_prefs_set_path(filepref, event->file ? event->file : ""); + g_free(filepref); + g_free(boolpref); + } + gnt_widget_destroy(GNT_WIDGET(win)); +} + +static void +file_cb(GntFileSel *w, const char *path, const char *file, gpointer data) +{ + FinchSoundEvent *event = data; + + g_free(event->file); + event->file = g_strdup(path); + + gnt_tree_change_text(GNT_TREE(pref_dialog->events), GINT_TO_POINTER(event->id), 1, file); + gnt_tree_set_choice(GNT_TREE(pref_dialog->events), GINT_TO_POINTER(event->id), TRUE); + + gnt_widget_destroy(GNT_WIDGET(w)); +} + +static void +test_cb(GntWidget *button, gpointer null) +{ + PurpleSoundEventID id = GPOINTER_TO_INT(gnt_tree_get_selection_data(GNT_TREE(pref_dialog->events))); + FinchSoundEvent * event = &sounds[id]; + char *enabled, *file, *tmpfile; + gboolean temp_value; + + enabled = g_strdup_printf(FINCH_PREFS_ROOT "/sound/profiles/%s/enabled/%s", + finch_sound_get_active_profile(), event->pref); + file = g_strdup_printf(FINCH_PREFS_ROOT "/sound/profiles/%s/file/%s", + finch_sound_get_active_profile(), event->pref); + + temp_value = purple_prefs_get_bool(enabled); + tmpfile = g_strdup(purple_prefs_get_string(file)); + + purple_prefs_set_string(file, event->file); + if (!temp_value) purple_prefs_set_bool(enabled, TRUE); + + purple_sound_play_event(id, NULL); + + if (!temp_value) purple_prefs_set_bool(enabled, FALSE); + purple_prefs_set_string(file, tmpfile); + + g_free(enabled); + g_free(file); + g_free(tmpfile); +} + +static void +reset_cb(GntWidget *button, gpointer null) +{ + /* Don't dereference this pointer ! */ + gpointer key = gnt_tree_get_selection_data(GNT_TREE(pref_dialog->events)); + + FinchSoundEvent * event = &sounds[GPOINTER_TO_INT(key)]; + g_free(event->file); + event->file = NULL; + gnt_tree_change_text(GNT_TREE(pref_dialog->events), key, 1, _("(default)")); +} + + +static void +choose_cb(GntWidget *button, gpointer null) +{ + GntWidget *w = gnt_file_sel_new(); + GntFileSel *sel = GNT_FILE_SEL(w); + PurpleSoundEventID id = GPOINTER_TO_INT(gnt_tree_get_selection_data(GNT_TREE(pref_dialog->events))); + FinchSoundEvent * event = &sounds[id]; + char *path = NULL; + + gnt_box_set_title(GNT_BOX(w), _("Select Sound File ...")); + gnt_file_sel_set_current_location(sel, + (event && event->file) ? (path = g_path_get_dirname(event->file)) + : purple_home_dir()); + + g_signal_connect_swapped(G_OBJECT(sel->cancel), "activate", G_CALLBACK(gnt_widget_destroy), sel); + g_signal_connect(G_OBJECT(sel), "file_selected", G_CALLBACK(file_cb), event); + g_signal_connect_swapped(G_OBJECT(sel), "destroy", G_CALLBACK(g_nullify_pointer), &pref_dialog->selector); + + /* If there's an already open file-selector, close that one. */ + if (pref_dialog->selector) + gnt_widget_destroy(pref_dialog->selector); + + pref_dialog->selector = w; + + gnt_widget_show(w); + g_free(path); } -PurpleSoundUiOps *finch_sound_get_ui_ops(void) +static void +release_pref_dialog(GntBindable *data, gpointer null) +{ + GList * itr; + for (itr = gnt_tree_get_rows(GNT_TREE(pref_dialog->events)); itr; itr = itr->next) { + PurpleSoundEventID id = GPOINTER_TO_INT(itr->data); + FinchSoundEvent * e = &sounds[id]; + g_free(e->file); + e->file = NULL; + } + if (pref_dialog->selector) + gnt_widget_destroy(pref_dialog->selector); + g_free(pref_dialog); + pref_dialog = NULL; +} + +static void +load_pref_window(const char * profile) { - return NULL; + gint i; + + finch_sound_set_active_profile(profile); + + gnt_combo_box_set_selected(GNT_COMBO_BOX(pref_dialog->method), (gchar *)purple_prefs_get_string(make_pref("/method"))); + + gnt_entry_set_text(GNT_ENTRY(pref_dialog->command), purple_prefs_get_path(make_pref("/command"))); + + gnt_check_box_set_checked(GNT_CHECK_BOX(pref_dialog->conv_focus), purple_prefs_get_bool(make_pref("/conv_focus"))); + + gnt_combo_box_set_selected(GNT_COMBO_BOX(pref_dialog->while_status), GINT_TO_POINTER(purple_prefs_get_int("/purple" "/sound/while_status"))); + + gnt_slider_set_value(GNT_SLIDER(pref_dialog->volume), CLAMP(purple_prefs_get_int(make_pref("/volume")), 0, 100)); + + for (i = 0; i < PURPLE_NUM_SOUNDS; i++) { + FinchSoundEvent * event = &sounds[i]; + gchar *boolpref; + gchar *filepref, *basename = NULL; + const char * profile = finch_sound_get_active_profile(); + + filepref = g_strdup_printf(FINCH_PREFS_ROOT "/sound/profiles/%s/file/%s", profile, event->pref); + + g_free(event->file); + event->file = g_strdup(purple_prefs_get_path(filepref)); + + g_free(filepref); + if (event->label == NULL) { + continue; + } + + boolpref = g_strdup_printf(FINCH_PREFS_ROOT "/sound/profiles/%s/enabled/%s", profile, event->pref); + + gnt_tree_change_text(GNT_TREE(pref_dialog->events), GINT_TO_POINTER(i), 0, event->label); + gnt_tree_change_text(GNT_TREE(pref_dialog->events), GINT_TO_POINTER(i), 1, + event->file[0] ? (basename = g_path_get_basename(event->file)) : _("(default)")); + g_free(basename); + + gnt_tree_set_choice(GNT_TREE(pref_dialog->events), GINT_TO_POINTER(i), purple_prefs_get_bool(boolpref)); + + g_free(boolpref); + } + + gnt_tree_set_selected(GNT_TREE(pref_dialog->profiles), (gchar *)finch_sound_get_active_profile()); + + gnt_widget_draw(pref_dialog->window); +} + +static void +reload_pref_window(const char *profile) +{ + if (!strcmp(profile, finch_sound_get_active_profile())) + return; + load_pref_window(profile); +} + +static void +prof_del_cb(GntWidget *button, gpointer null) +{ + const char * profile = gnt_entry_get_text(GNT_ENTRY(pref_dialog->new_profile)); + gchar * pref; + + if (!strcmp(profile, DEFAULT_PROFILE)) + return; + + pref = g_strdup_printf(FINCH_PREFS_ROOT "/sound/profiles/%s", profile); + purple_prefs_remove(pref); + g_free(pref); + + if (!strcmp(pref_dialog->original_profile, profile)) { + g_free(pref_dialog->original_profile); + pref_dialog->original_profile = g_strdup(DEFAULT_PROFILE); + } + + if(!strcmp(profile, finch_sound_get_active_profile())) + reload_pref_window(DEFAULT_PROFILE); + + gnt_tree_remove(GNT_TREE(pref_dialog->profiles), (gchar *) profile); +} + +static void +prof_add_cb(GntButton *button, GntEntry * entry) +{ + const char * profile = gnt_entry_get_text(entry); + GntTreeRow * row; + if (!finch_sound_profile_exists(profile)) { + gpointer key = g_strdup(profile); + row = gnt_tree_create_row(GNT_TREE(pref_dialog->profiles), profile); + gnt_tree_add_row_after(GNT_TREE(pref_dialog->profiles), key, + row, + NULL, NULL); + gnt_entry_set_text(entry, ""); + gnt_tree_set_selected(GNT_TREE(pref_dialog->profiles), key); + finch_sound_set_active_profile(key); + } else + reload_pref_window(profile); +} + +static void +prof_load_cb(GntTree *tree, gpointer oldkey, gpointer newkey, gpointer null) +{ + reload_pref_window(newkey); +} + +static void +cancel_cb(GntButton *button, gpointer win) +{ + finch_sound_set_active_profile(pref_dialog->original_profile); + gnt_widget_destroy(GNT_WIDGET(win)); } -void finch_sounds_show_all(void) +void +finch_sounds_show_all(void) { + GntWidget *box, *tmpbox, *splitbox, *cmbox, *slider; + GntWidget *entry; + GntWidget *chkbox; + GntWidget *button; + GntWidget *label; + GntWidget *tree; + GntWidget *win; + + gint i; + GList *itr, *list; + + if (pref_dialog) { + gnt_window_present(pref_dialog->window); + return; + } + + pref_dialog = g_new0(SoundPrefDialog, 1); + + pref_dialog->original_profile = g_strdup(finch_sound_get_active_profile()); + + pref_dialog->window = win = gnt_window_box_new(FALSE, TRUE); + gnt_box_set_pad(GNT_BOX(win), 0); + gnt_box_set_toplevel(GNT_BOX(win), TRUE); + gnt_box_set_title(GNT_BOX(win), _("Sound Preferences")); + gnt_box_set_fill(GNT_BOX(win), TRUE); + gnt_box_set_alignment(GNT_BOX(win), GNT_ALIGN_MID); + + /* Profiles */ + splitbox = gnt_hbox_new(FALSE); + gnt_box_set_pad(GNT_BOX(splitbox), 0); + gnt_box_set_alignment(GNT_BOX(splitbox), GNT_ALIGN_TOP); + + box = gnt_vbox_new(FALSE); + gnt_box_set_pad(GNT_BOX(box), 0); + gnt_box_add_widget(GNT_BOX(box), gnt_label_new_with_format(_("Profiles"), GNT_TEXT_FLAG_BOLD)); + pref_dialog->profiles = tree = gnt_tree_new(); + gnt_tree_set_hash_fns(GNT_TREE(tree), g_str_hash, g_str_equal, g_free); + gnt_tree_set_compare_func(GNT_TREE(tree), (GCompareFunc)g_ascii_strcasecmp); + g_signal_connect(G_OBJECT(tree), "selection-changed", G_CALLBACK(prof_load_cb), NULL); + + itr = list = finch_sound_get_profiles(); + for (; itr; itr = itr->next) { + /* Do not free itr->data. It's the stored as a key for the tree, and will + * be freed when the tree is destroyed. */ + gnt_tree_add_row_after(GNT_TREE(tree), itr->data, + gnt_tree_create_row(GNT_TREE(tree), itr->data), NULL, NULL); + } + g_list_free(list); + + gnt_box_add_widget(GNT_BOX(box), tree); + + pref_dialog->new_profile = entry = gnt_entry_new(""); + gnt_box_add_widget(GNT_BOX(box), entry); + + tmpbox = gnt_hbox_new(FALSE); + button = gnt_button_new("Add"); + g_signal_connect(G_OBJECT(button), "activate", G_CALLBACK(prof_add_cb), entry); + gnt_box_add_widget(GNT_BOX(tmpbox), button); + button = gnt_button_new("Delete"); + g_signal_connect(G_OBJECT(button), "activate", G_CALLBACK(prof_del_cb), NULL); + gnt_box_add_widget(GNT_BOX(tmpbox), button); + gnt_box_add_widget(GNT_BOX(box), tmpbox); + gnt_box_add_widget(GNT_BOX(splitbox), box); + + gnt_box_add_widget(GNT_BOX(splitbox), gnt_vline_new()); + + /* Sound method */ + + box = gnt_vbox_new(FALSE); + gnt_box_set_pad(GNT_BOX(box), 0); + + pref_dialog->method = cmbox = gnt_combo_box_new(); + gnt_tree_set_hash_fns(GNT_TREE(GNT_COMBO_BOX(cmbox)->dropdown), g_str_hash, g_str_equal, NULL); + gnt_combo_box_add_data(GNT_COMBO_BOX(cmbox), "automatic", _("Automatic")); + gnt_combo_box_add_data(GNT_COMBO_BOX(cmbox), "alsa", "ALSA"); + gnt_combo_box_add_data(GNT_COMBO_BOX(cmbox), "esd", "ESD"); + gnt_combo_box_add_data(GNT_COMBO_BOX(cmbox), "beep", _("Console Beep")); + gnt_combo_box_add_data(GNT_COMBO_BOX(cmbox), "custom", _("Command")); + gnt_combo_box_add_data(GNT_COMBO_BOX(cmbox), "nosound", _("No Sound")); + + label = gnt_label_new_with_format(_("Sound Method"), GNT_TEXT_FLAG_BOLD); + gnt_box_add_widget(GNT_BOX(box), label); + tmpbox = gnt_hbox_new(TRUE); + gnt_box_set_fill(GNT_BOX(tmpbox), FALSE); + gnt_box_set_pad(GNT_BOX(tmpbox), 0); + gnt_box_add_widget(GNT_BOX(tmpbox), gnt_label_new(_("Method: "))); + gnt_box_add_widget(GNT_BOX(tmpbox), cmbox); + gnt_box_add_widget(GNT_BOX(box), tmpbox); + + tmpbox = gnt_hbox_new(TRUE); + gnt_box_set_pad(GNT_BOX(tmpbox), 0); + gnt_box_set_fill(GNT_BOX(tmpbox), FALSE); + gnt_box_add_widget(GNT_BOX(tmpbox), gnt_label_new(_("Sound Command\n(%s for filename)"))); + pref_dialog->command = entry = gnt_entry_new(""); + gnt_box_add_widget(GNT_BOX(tmpbox), entry); + gnt_box_add_widget(GNT_BOX(box), tmpbox); + + gnt_box_add_widget(GNT_BOX(box), gnt_line_new(FALSE)); + + /* Sound options */ + gnt_box_add_widget(GNT_BOX(box), gnt_label_new_with_format(_("Sound Options"), GNT_TEXT_FLAG_BOLD)); + pref_dialog->conv_focus = chkbox = gnt_check_box_new(_("Sounds when conversation has focus")); + gnt_box_add_widget(GNT_BOX(box), chkbox); + + tmpbox = gnt_hbox_new(TRUE); + gnt_box_set_pad(GNT_BOX(tmpbox), 0); + gnt_box_set_fill(GNT_BOX(tmpbox), FALSE); + gnt_box_add_widget(GNT_BOX(tmpbox), gnt_label_new("Enable Sounds:")); + pref_dialog->while_status = cmbox = gnt_combo_box_new(); + gnt_combo_box_add_data(GNT_COMBO_BOX(cmbox), GINT_TO_POINTER(3), _("Always")); + gnt_combo_box_add_data(GNT_COMBO_BOX(cmbox), GINT_TO_POINTER(1), _("Only when available")); + gnt_combo_box_add_data(GNT_COMBO_BOX(cmbox), GINT_TO_POINTER(2), _("Only when not available")); + gnt_box_add_widget(GNT_BOX(tmpbox), cmbox); + gnt_box_add_widget(GNT_BOX(box), tmpbox); + + tmpbox = gnt_hbox_new(TRUE); + gnt_box_set_pad(GNT_BOX(tmpbox), 0); + gnt_box_set_fill(GNT_BOX(tmpbox), FALSE); + gnt_box_add_widget(GNT_BOX(tmpbox), gnt_label_new(_("Volume(0-100):"))); + + pref_dialog->volume = slider = gnt_slider_new(FALSE, 100, 0); + gnt_slider_set_step(GNT_SLIDER(slider), 5); + label = gnt_label_new(""); + gnt_slider_reflect_label(GNT_SLIDER(slider), GNT_LABEL(label)); + gnt_box_set_pad(GNT_BOX(tmpbox), 1); + gnt_box_add_widget(GNT_BOX(tmpbox), slider); + gnt_box_add_widget(GNT_BOX(tmpbox), label); + gnt_box_add_widget(GNT_BOX(box), tmpbox); + gnt_box_add_widget(GNT_BOX(splitbox), box); + + gnt_box_add_widget(GNT_BOX(win), splitbox); + + gnt_box_add_widget(GNT_BOX(win), gnt_hline_new()); + + /* Sound events */ + gnt_box_add_widget(GNT_BOX(win), gnt_label_new_with_format(_("Sound Events"), GNT_TEXT_FLAG_BOLD)); + pref_dialog->events = tree = gnt_tree_new_with_columns(2); + gnt_tree_set_column_titles(GNT_TREE(tree), _("Event"), _("File")); + gnt_tree_set_show_title(GNT_TREE(tree), TRUE); + + for (i = 0; i < PURPLE_NUM_SOUNDS; i++) { + FinchSoundEvent * event = &sounds[i]; + + if (event->label == NULL) { + continue; + } + + gnt_tree_add_choice(GNT_TREE(tree), GINT_TO_POINTER(i), + gnt_tree_create_row(GNT_TREE(tree), "", ""), + NULL, NULL); + } + + gnt_tree_adjust_columns(GNT_TREE(tree)); + gnt_box_add_widget(GNT_BOX(win), tree); + + box = gnt_hbox_new(FALSE); + button = gnt_button_new(_("Test")); + g_signal_connect(G_OBJECT(button), "activate", G_CALLBACK(test_cb), NULL); + gnt_box_add_widget(GNT_BOX(box), button); + button = gnt_button_new(_("Reset")); + g_signal_connect(G_OBJECT(button), "activate", G_CALLBACK(reset_cb), NULL); + gnt_box_add_widget(GNT_BOX(box), button); + button = gnt_button_new(_("Choose...")); + g_signal_connect(G_OBJECT(button), "activate", G_CALLBACK(choose_cb), NULL); + gnt_box_add_widget(GNT_BOX(box), button); + gnt_box_add_widget(GNT_BOX(win), box); + + gnt_box_add_widget(GNT_BOX(win), gnt_line_new(FALSE)); + + /* Add new stuff before this */ + box = gnt_hbox_new(FALSE); + gnt_box_set_fill(GNT_BOX(box), TRUE); + button = gnt_button_new(_("Save")); + g_signal_connect(G_OBJECT(button), "activate", G_CALLBACK(save_cb), win); + gnt_box_add_widget(GNT_BOX(box), button); + button = gnt_button_new(_("Cancel")); + g_signal_connect(G_OBJECT(button), "activate", G_CALLBACK(cancel_cb), win); + gnt_box_add_widget(GNT_BOX(box), button); + gnt_box_add_widget(GNT_BOX(win), box); + + g_signal_connect(G_OBJECT(win), "destroy", G_CALLBACK(release_pref_dialog), NULL); + + load_pref_window(finch_sound_get_active_profile()); + + gnt_widget_show(win); +} + +static PurpleSoundUiOps sound_ui_ops = +{ + finch_sound_init, + finch_sound_uninit, + finch_sound_play_file, + finch_sound_play_event, + NULL, + NULL, + NULL, + NULL +}; + +PurpleSoundUiOps * +finch_sound_get_ui_ops(void) +{ + return &sound_ui_ops; }
--- a/finch/gntui.c Sat Aug 11 21:08:06 2007 +0000 +++ b/finch/gntui.c Sat Aug 11 21:08:27 2007 +0000 @@ -35,6 +35,7 @@ #include "gntprefs.h" #include "gntrequest.h" #include "gntstatus.h" +#include "gntsound.h" #include <prefs.h> @@ -58,6 +59,9 @@ finch_blist_init(); purple_blist_set_ui_ops(finch_blist_get_ui_ops()); + /* Initialize sound */ + purple_sound_set_ui_ops(finch_sound_get_ui_ops()); + /* Now the conversations */ finch_conversation_init(); purple_conversations_set_ui_ops(finch_conv_get_ui_ops()); @@ -80,6 +84,7 @@ gnt_register_action(_("Debug Window"), finch_debug_window_show); gnt_register_action(_("File Transfers"), finch_xfer_dialog_show); gnt_register_action(_("Plugins"), finch_plugins_show_all); + gnt_register_action(_("Sounds"), finch_sounds_show_all); gnt_register_action(_("Preferences"), finch_prefs_show_all); gnt_register_action(_("Statuses"), finch_savedstatus_show_all);
--- a/finch/libgnt/configure.ac Sat Aug 11 21:08:06 2007 +0000 +++ b/finch/libgnt/configure.ac Sat Aug 11 21:08:27 2007 +0000 @@ -26,7 +26,7 @@ m4_define([gnt_lt_current], [1]) m4_define([gnt_major_version], [2]) -m4_define([gnt_minor_version], [0]) +m4_define([gnt_minor_version], [1]) m4_define([gnt_micro_version], [0]) m4_define([gnt_version_suffix], [devel]) m4_define([gnt_version], @@ -36,6 +36,7 @@ AC_INIT([libgnt], [gnt_display_version], [devel@pidgin.im]) AC_CANONICAL_SYSTEM AM_CONFIG_HEADER(config.h) +AC_CONFIG_AUX_DIR([.]) AM_INIT_AUTOMAKE(AC_PACKAGE_NAME, AC_PACKAGE_VERSION) GNT_MAJOR_VERSION=gnt_major_version @@ -314,6 +315,8 @@ AC_DEFINE(NO_LIBXML, 1, [Do not have libxml2.]) fi +AM_CONDITIONAL(PURPLE_AVAILABLE, false) + AC_OUTPUT([Makefile gnt.pc wms/Makefile
--- a/finch/libgnt/gnt.h Sat Aug 11 21:08:06 2007 +0000 +++ b/finch/libgnt/gnt.h Sat Aug 11 21:08:27 2007 +0000 @@ -157,4 +157,3 @@ void (*callback)(int status, gpointer data), gpointer data); gboolean gnt_is_refugee(void); -
--- a/finch/libgnt/gntbindable.c Sat Aug 11 21:08:06 2007 +0000 +++ b/finch/libgnt/gntbindable.c Sat Aug 11 21:08:27 2007 +0000 @@ -20,13 +20,182 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include <string.h> + #include "gntbindable.h" #include "gntstyle.h" #include "gnt.h" #include "gntutils.h" +#include "gnttextview.h" +#include "gnttree.h" +#include "gntbox.h" +#include "gntbutton.h" +#include "gntwindow.h" +#include "gntlabel.h" static GObjectClass *parent_class = NULL; +static struct +{ + char * okeys; /* Old keystrokes */ + char * keys; /* New Keystrokes being bound to the action */ + GntBindableClass * klass; /* Class of the object that's getting keys rebound */ + char * name; /* The name of the action */ + GList * params; /* The list of paramaters */ +} rebind_info; + +static void +gnt_bindable_free_rebind_info() +{ + g_free(rebind_info.name); + g_free(rebind_info.keys); + g_free(rebind_info.okeys); +} + +static void +gnt_bindable_rebinding_cancel(GntWidget *button, gpointer data) +{ + gnt_bindable_free_rebind_info(); + gnt_widget_destroy(GNT_WIDGET(data)); +} + +static void +gnt_bindable_rebinding_rebind(GntWidget *button, gpointer data) +{ + if (rebind_info.keys) { + gnt_bindable_register_binding(rebind_info.klass, + NULL, + rebind_info.okeys, + rebind_info.params); + gnt_bindable_register_binding(rebind_info.klass, + rebind_info.name, + rebind_info.keys, + rebind_info.params); + } + gnt_bindable_free_rebind_info(); + gnt_widget_destroy(GNT_WIDGET(data)); +} + +static gboolean +gnt_bindable_rebinding_grab_key(GntBindable *bindable, const char *text, gpointer data) +{ + GntTextView *textview = GNT_TEXT_VIEW(data); + char *new_text; + const char *tmp; + + if (text && *text) { + /* Rebinding tab or enter for something is probably not that great an idea */ + if (!strcmp(text, GNT_KEY_CTRL_I) || !strcmp(text, GNT_KEY_ENTER)) { + return FALSE; + } + + tmp = gnt_key_lookup(text); + new_text = g_strdup_printf("KEY: \"%s\"", tmp); + gnt_text_view_clear(textview); + gnt_text_view_append_text_with_flags(textview, new_text, GNT_TEXT_FLAG_NORMAL); + g_free(new_text); + + g_free(rebind_info.keys); + rebind_info.keys = g_strdup(text); + + return TRUE; + } + return FALSE; +} +static void +gnt_bindable_rebinding_activate(GntBindable *data, gpointer bindable) +{ + const char *widget_name = g_type_name(G_OBJECT_TYPE(bindable)); + char *keys; + GntWidget *key_textview; + GntWidget *label; + GntWidget *bind_button, *cancel_button; + GntWidget *button_box; + GList *current_row_data; + char *tmp; + GntWidget *win = gnt_window_new(); + GntTree *tree = GNT_TREE(data); + GntWidget *vbox = gnt_box_new(FALSE, TRUE); + + rebind_info.klass = GNT_BINDABLE_GET_CLASS(bindable); + + current_row_data = gnt_tree_get_selection_text_list(tree); + rebind_info.name = g_strdup(g_list_nth_data(current_row_data, 1)); + + keys = gnt_tree_get_selection_data(tree); + rebind_info.okeys = g_strdup(gnt_key_translate(keys)); + + rebind_info.params = NULL; + + g_list_foreach(current_row_data, (GFunc)g_free, NULL); + g_list_free(current_row_data); + + gnt_box_set_alignment(GNT_BOX(vbox), GNT_ALIGN_MID); + + gnt_box_set_title(GNT_BOX(win), "Key Capture"); + + tmp = g_strdup_printf("Type the new bindings for %s in a %s.", rebind_info.name, widget_name); + label = gnt_label_new(tmp); + g_free(tmp); + gnt_box_add_widget(GNT_BOX(vbox), label); + + tmp = g_strdup_printf("KEY: \"%s\"", keys); + key_textview = gnt_text_view_new(); + gnt_widget_set_size(key_textview, key_textview->priv.x, 2); + gnt_text_view_append_text_with_flags(GNT_TEXT_VIEW(key_textview), tmp, GNT_TEXT_FLAG_NORMAL); + g_free(tmp); + gnt_widget_set_name(key_textview, "keystroke"); + gnt_box_add_widget(GNT_BOX(vbox), key_textview); + + g_signal_connect(G_OBJECT(win), "key_pressed", G_CALLBACK(gnt_bindable_rebinding_grab_key), key_textview); + + button_box = gnt_box_new(FALSE, FALSE); + + bind_button = gnt_button_new("BIND"); + gnt_widget_set_name(bind_button, "bind"); + gnt_box_add_widget(GNT_BOX(button_box), bind_button); + + cancel_button = gnt_button_new("Cancel"); + gnt_widget_set_name(cancel_button, "cancel"); + gnt_box_add_widget(GNT_BOX(button_box), cancel_button); + + g_signal_connect(G_OBJECT(bind_button), "activate", G_CALLBACK(gnt_bindable_rebinding_rebind), win); + g_signal_connect(G_OBJECT(cancel_button), "activate", G_CALLBACK(gnt_bindable_rebinding_cancel), win); + + gnt_box_add_widget(GNT_BOX(vbox), button_box); + + gnt_box_add_widget(GNT_BOX(win), vbox); + gnt_widget_show(win); +} + +typedef struct +{ + GHashTable *hash; + GntTree *tree; +} BindingView; + +static void +add_binding(gpointer key, gpointer value, gpointer data) +{ + BindingView *bv = data; + GntBindableActionParam *act = value; + const char *name = g_hash_table_lookup(bv->hash, act->action); + if (name && *name) { + const char *k = gnt_key_lookup(key); + if (!k) + k = key; + gnt_tree_add_row_after(bv->tree, (gpointer)k, + gnt_tree_create_row(bv->tree, k, name), NULL, NULL); + } +} + +static void +add_action(gpointer key, gpointer value, gpointer data) +{ + BindingView *bv = data; + g_hash_table_insert(bv->hash, value, key); +} + static void gnt_bindable_class_init(GntBindableClass *klass) { @@ -88,7 +257,7 @@ { static GType type = 0; - if(type == 0) { + if (type == 0) { static const GTypeInfo info = { sizeof(GntBindableClass), (GBaseInitFunc)duplicate_hashes, /* base_init */ @@ -251,4 +420,55 @@ g_free(param); } +GntBindable * gnt_bindable_bindings_view(GntBindable *bind) +{ + GntBindable *tree = GNT_BINDABLE(gnt_tree_new_with_columns(2)); + GntBindableClass *klass = GNT_BINDABLE_CLASS(GNT_BINDABLE_GET_CLASS(bind)); + GHashTable *hash = g_hash_table_new(g_direct_hash, g_direct_equal); + BindingView bv = {hash, GNT_TREE(tree)}; + gnt_tree_set_compare_func(bv.tree, (GCompareFunc)g_utf8_collate); + g_hash_table_foreach(klass->actions, add_action, &bv); + g_hash_table_foreach(klass->bindings, add_binding, &bv); + if (GNT_TREE(tree)->list == NULL) { + gnt_widget_destroy(GNT_WIDGET(tree)); + tree = NULL; + } else + gnt_tree_adjust_columns(bv.tree); + g_hash_table_destroy(hash); + + return tree; +} + +static void +reset_binding_window(GntBindableClass *window, gpointer k) +{ + GntBindableClass *klass = GNT_BINDABLE_CLASS(k); + klass->help_window = NULL; +} + +gboolean +gnt_bindable_build_help_window(GntBindable *bindable) +{ + GntWidget *tree; + GntBindableClass *klass = GNT_BINDABLE_GET_CLASS(bindable); + char *title; + + tree = GNT_WIDGET(gnt_bindable_bindings_view(bindable)); + + klass->help_window = GNT_BINDABLE(gnt_window_new()); + title = g_strdup_printf("Bindings for %s", g_type_name(G_OBJECT_TYPE(bindable))); + gnt_box_set_title(GNT_BOX(klass->help_window), title); + if (tree) { + g_signal_connect(G_OBJECT(tree), "activate", G_CALLBACK(gnt_bindable_rebinding_activate), bindable); + gnt_box_add_widget(GNT_BOX(klass->help_window), tree); + } else + gnt_box_add_widget(GNT_BOX(klass->help_window), gnt_label_new("This widget has no customizable bindings.")); + + g_signal_connect(G_OBJECT(klass->help_window), "destroy", G_CALLBACK(reset_binding_window), klass); + gnt_widget_show(GNT_WIDGET(klass->help_window)); + g_free(title); + + return TRUE; +} +
--- a/finch/libgnt/gntbindable.h Sat Aug 11 21:08:06 2007 +0000 +++ b/finch/libgnt/gntbindable.h Sat Aug 11 21:08:27 2007 +0000 @@ -57,7 +57,8 @@ GHashTable *actions; /* name -> Action */ GHashTable *bindings; /* key -> ActionParam */ - void (*gnt_reserved1)(void); + GntBindable * help_window; + void (*gnt_reserved2)(void); void (*gnt_reserved3)(void); void (*gnt_reserved4)(void); @@ -150,6 +151,31 @@ */ gboolean gnt_bindable_perform_action_named(GntBindable *bindable, const char *name, ...); +/** +* Returns a GntTree populated with "key" -> "binding" for the widget. +*/ +/** +* +* @param widget +* +* @return +*/ +GntBindable * gnt_bindable_bindings_view(GntBindable *bind); + +/** + * + * Builds a window that list the key bindings for a GntBindable object. From this window a user can select a listing to rebind a new key for the given action. + * + */ +/** + * + * @param bindable + * + * @return + */ + +gboolean gnt_bindable_build_help_window(GntBindable *bindable); + G_END_DECLS #endif /* GNT_BINDABLE_H */
--- a/finch/libgnt/gntcombobox.h Sat Aug 11 21:08:06 2007 +0000 +++ b/finch/libgnt/gntcombobox.h Sat Aug 11 21:08:27 2007 +0000 @@ -69,7 +69,8 @@ G_BEGIN_DECLS /** - * + * + * Get the GType for GntComboBox * * @return */ @@ -77,44 +78,55 @@ /** * + * Create a new GntComboBox * - * @return + * @return A new GntComboBox */ GntWidget * gnt_combo_box_new(void); /** * - * @param box - * @param key - * @param text + * Add an entry + * + * @param box The GntComboBox + * @param key The data + * @param text The text to display */ void gnt_combo_box_add_data(GntComboBox *box, gpointer key, const char *text); /** + * + * Remove an entry * - * @param box - * @param key + * @param box The GntComboBox + * @param key The data to be removed */ void gnt_combo_box_remove(GntComboBox *box, gpointer key); /** * - * @param box + * Remove all entries + * + * @param box The GntComboBox */ void gnt_combo_box_remove_all(GntComboBox *box); /** * - * @param box + * Get the data that is currently selected * - * @return + * @param box The GntComboBox + * + * @return The data of the currently selected entry */ gpointer gnt_combo_box_get_selected_data(GntComboBox *box); /** * - * @param box - * @param key + * Set the current selection to a specific entry + * + * @param box The GntComboBox + * @param key The data to be set to */ void gnt_combo_box_set_selected(GntComboBox *box, gpointer key);
--- a/finch/libgnt/gntfilesel.c Sat Aug 11 21:08:06 2007 +0000 +++ b/finch/libgnt/gntfilesel.c Sat Aug 11 21:08:27 2007 +0000 @@ -355,6 +355,7 @@ } else if (strcmp(str, "..") == 0) { gnt_tree_set_selected(tree, dir); } + gnt_bindable_perform_action_named(GNT_BINDABLE(tree), "end-search", NULL); g_free(dir); g_free(str); g_free(path); @@ -495,10 +496,11 @@ if (!sel->multiselect) return FALSE; tree = sel->dirsonly ? sel->dirs : sel->files; - if (!gnt_widget_has_focus(tree)) + if (!gnt_widget_has_focus(tree) || + gnt_tree_is_searching(GNT_TREE(tree))) return FALSE; - file = gnt_tree_get_selection_data(sel->dirsonly ? GNT_TREE(sel->dirs) : GNT_TREE(sel->files)); + file = gnt_tree_get_selection_data(GNT_TREE(tree)); str = gnt_file_sel_get_selected_file(sel); if ((find = g_list_find_custom(sel->tags, str, (GCompareFunc)g_utf8_collate)) != NULL) { @@ -526,7 +528,8 @@ if (!sel->multiselect) return FALSE; tree = sel->dirsonly ? sel->dirs : sel->files; - if (!gnt_widget_has_focus(tree)) + if (!gnt_widget_has_focus(tree) || + gnt_tree_is_searching(GNT_TREE(tree))) return FALSE; g_list_foreach(sel->tags, (GFunc)g_free, NULL); @@ -547,6 +550,9 @@ if (!gnt_widget_has_focus(sel->dirs) && !gnt_widget_has_focus(sel->files)) return FALSE; + if (gnt_tree_is_searching(GNT_TREE(sel->dirs)) || + gnt_tree_is_searching(GNT_TREE(sel->files))) + return FALSE; path = g_build_filename(sel->current, "..", NULL); dir = g_path_get_basename(sel->current);
--- a/finch/libgnt/gntline.c Sat Aug 11 21:08:06 2007 +0000 +++ b/finch/libgnt/gntline.c Sat Aug 11 21:08:27 2007 +0000 @@ -24,6 +24,12 @@ enum { + PROP_0, + PROP_VERTICAL +}; + +enum +{ SIGS = 1, }; @@ -65,14 +71,57 @@ } static void +gnt_line_set_property(GObject *obj, guint prop_id, const GValue *value, + GParamSpec *spec) +{ + GntLine *line = GNT_LINE(obj); + switch (prop_id) { + case PROP_VERTICAL: + line->vertical = g_value_get_boolean(value); + if (line->vertical) { + GNT_WIDGET_SET_FLAGS(line, GNT_WIDGET_GROW_Y); + } else { + GNT_WIDGET_SET_FLAGS(line, GNT_WIDGET_GROW_X); + } + break; + default: + break; + } +} + +static void +gnt_line_get_property(GObject *obj, guint prop_id, GValue *value, + GParamSpec *spec) +{ + GntLine *line = GNT_LINE(obj); + switch (prop_id) { + case PROP_VERTICAL: + g_value_set_boolean(value, line->vertical); + break; + default: + break; + } +} + +static void gnt_line_class_init(GntLineClass *klass) { + GObjectClass *gclass = G_OBJECT_CLASS(klass); parent_class = GNT_WIDGET_CLASS(klass); parent_class->draw = gnt_line_draw; parent_class->map = gnt_line_map; parent_class->size_request = gnt_line_size_request; - GNTDEBUG; + gclass->set_property = gnt_line_set_property; + gclass->get_property = gnt_line_get_property; + g_object_class_install_property(gclass, + PROP_VERTICAL, + g_param_spec_boolean("vertical", "Vertical", + "Whether it's a vertical line or a horizontal one.", + TRUE, + G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB + ) + ); } static void @@ -118,20 +167,7 @@ GntWidget *gnt_line_new(gboolean vertical) { - GntWidget *widget = g_object_new(GNT_TYPE_LINE, NULL); - GntLine *line = GNT_LINE(widget); - - line->vertical = vertical; - - if (vertical) - { - GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_GROW_Y); - } - else - { - GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_GROW_X); - } - + GntWidget *widget = g_object_new(GNT_TYPE_LINE, "vertical", vertical, NULL); return widget; }
--- a/finch/libgnt/gntmain.c Sat Aug 11 21:08:06 2007 +0000 +++ b/finch/libgnt/gntmain.c Sat Aug 11 21:08:27 2007 +0000 @@ -252,6 +252,18 @@ if (HOLDING_ESCAPE) keys[0] = '\033'; k = keys; + +#if 0 + /* I am not sure what's happening here. If this actually does something, + * then this needs to go in gnt_keys_refine. */ + if (*k < 0) { /* Alt not sending ESC* */ + *(k + 1) = 128 - *k; + *k = 27; + *(k + 2) = 0; + rd++; + } +#endif + while (rd) { char back; int p;
--- a/finch/libgnt/gntmenu.c Sat Aug 11 21:08:06 2007 +0000 +++ b/finch/libgnt/gntmenu.c Sat Aug 11 21:08:27 2007 +0000 @@ -247,11 +247,14 @@ int current = menu->selected; if (menu->submenu) { - do menu = menu->submenu; while (menu->submenu); - return (gnt_widget_key_pressed(GNT_WIDGET(menu), text)); + GntMenu *sub = menu; + do sub = sub->submenu; while (sub->submenu); + if (gnt_widget_key_pressed(GNT_WIDGET(sub), text)) + return TRUE; } - if (text[0] == 27 && text[1] == 0) { + if ((text[0] == 27 && text[1] == 0) || + (menu->type != GNT_MENU_TOPLEVEL && strcmp(text, GNT_KEY_LEFT) == 0)) { /* Escape closes menu */ GntMenu *par = menu->parentmenu; if (par != NULL) { @@ -271,11 +274,17 @@ menu->selected++; if (menu->selected >= g_list_length(menu->list)) menu->selected = 0; - } else if (strcmp(text, GNT_KEY_ENTER) == 0) { + } else if (strcmp(text, GNT_KEY_ENTER) == 0 || + strcmp(text, GNT_KEY_DOWN) == 0) { gnt_widget_activate(widget); } if (current != menu->selected) { + GntMenu *sub = menu->submenu; + while (sub) { + gnt_widget_hide(GNT_WIDGET(sub)); + sub = sub->submenu; + } gnt_widget_draw(widget); return TRUE; } @@ -283,6 +292,12 @@ if (text[1] == '\0') { if (check_for_trigger(menu, text[0])) return TRUE; + } else if (strcmp(text, GNT_KEY_RIGHT) == 0) { + GntMenuItem *item = gnt_tree_get_selection_data(GNT_TREE(menu)); + if (item && item->submenu) { + menuitem_activate(menu, item); + return TRUE; + } } return org_key_pressed(widget, text); }
--- a/finch/libgnt/gntutils.h Sat Aug 11 21:08:06 2007 +0000 +++ b/finch/libgnt/gntutils.h Sat Aug 11 21:08:27 2007 +0000 @@ -40,6 +40,7 @@ */ void gnt_util_get_text_bound(const char *text, int *width, int *height); +/* excluding *end */ /** * Get the onscreen width of a string, or a substring. *
--- a/finch/libgnt/gntwm.c Sat Aug 11 21:08:06 2007 +0000 +++ b/finch/libgnt/gntwm.c Sat Aug 11 21:08:27 2007 +0000 @@ -39,6 +39,8 @@ #include "gntmarshal.h" #include "gnt.h" #include "gntbox.h" +#include "gntbutton.h" +#include "gntentry.h" #include "gntlabel.h" #include "gntmenu.h" #include "gnttextview.h" @@ -84,6 +86,7 @@ static time_t last_active_time; static gboolean idle_update; static GList *act = NULL; /* list of WS with unseen activitiy */ +static gboolean ignore_keys = FALSE; static GList * g_list_bring_to_front(GList *list, gpointer data) @@ -485,35 +488,6 @@ return TRUE; } -static gboolean -help_for_widget(GntBindable *bindable, GList *null) -{ - GntWM *wm = GNT_WM(bindable); - GntWidget *widget, *tree, *win, *active; - char *title; - - if (!wm->cws->ordered) - return TRUE; - - widget = wm->cws->ordered->data; - if (!GNT_IS_BOX(widget)) - return TRUE; - active = GNT_BOX(widget)->active; - - tree = gnt_widget_bindings_view(active); - win = gnt_window_new(); - title = g_strdup_printf("Bindings for %s", g_type_name(G_OBJECT_TYPE(active))); - gnt_box_set_title(GNT_BOX(win), title); - if (tree) - gnt_box_add_widget(GNT_BOX(win), tree); - else - gnt_box_add_widget(GNT_BOX(win), gnt_label_new("This widget has no customizable bindings.")); - - gnt_widget_show(win); - - return TRUE; -} - static void destroy__list(GntWidget *widget, GntWM *wm) { @@ -843,6 +817,7 @@ shift_right(GntBindable *bindable, GList *null) { GntWM *wm = GNT_WM(bindable); + if (wm->_list.window) return TRUE; @@ -1138,6 +1113,74 @@ return TRUE; } +static gboolean +ignore_keys_start(GntBindable *bindable, GList *n) +{ + GntWM *wm = GNT_WM(bindable); + + if(!wm->menu && !wm->_list.window && wm->mode == GNT_KP_MODE_NORMAL){ + ignore_keys = TRUE; + return TRUE; + } + return FALSE; +} + +static gboolean +ignore_keys_end(GntBindable *bindable, GList *n) +{ + return ignore_keys ? !(ignore_keys = FALSE) : FALSE; +} + +static gboolean +help_for_bindable(GntWM *wm, GntBindable *bindable) +{ + gboolean ret = TRUE; + GntBindableClass *klass = GNT_BINDABLE_GET_CLASS(bindable); + + if (klass->help_window) { + gnt_wm_raise_window(wm, GNT_WIDGET(klass->help_window)); + } else { + ret = gnt_bindable_build_help_window(bindable); + } + return ret; +} + +static gboolean +help_for_wm(GntBindable *bindable, GList *null) +{ + return help_for_bindable(GNT_WM(bindable),bindable); +} + +static gboolean +help_for_window(GntBindable *bindable, GList *null) +{ + GntWM *wm = GNT_WM(bindable); + GntWidget *widget; + + if(!wm->cws->ordered) + return FALSE; + + widget = wm->cws->ordered->data; + + return help_for_bindable(wm,GNT_BINDABLE(widget)); +} + +static gboolean +help_for_widget(GntBindable *bindable, GList *null) +{ + GntWM *wm = GNT_WM(bindable); + GntWidget *widget; + + if (!wm->cws->ordered) + return TRUE; + + widget = wm->cws->ordered->data; + if (!GNT_IS_BOX(widget)) + return TRUE; + + return help_for_bindable(wm, GNT_BINDABLE(GNT_BOX(widget)->active)); +} + static void accumulate_windows(gpointer window, gpointer node, gpointer p) { @@ -1321,8 +1364,16 @@ "\033" "T", NULL); gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "workspace-list", workspace_list, "\033" "s", NULL); - gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "toggle-clipboard", - toggle_clipboard, "\033" "C", NULL); + gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "toggle-clipboard", toggle_clipboard, + "\033" "C", NULL); + gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "help-for-wm", help_for_wm, + "\033" "\\", NULL); + gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "help-for-window", help_for_window, + "\033" "|", NULL); + gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "ignore-keys-start", ignore_keys_start, + GNT_KEY_CTRL_G, NULL); + gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "ignore-keys-end", ignore_keys_end, + "\033" GNT_KEY_CTRL_G, NULL); gnt_style_read_actions(G_OBJECT_CLASS_TYPE(klass), GNT_BINDABLE_CLASS(klass)); @@ -1709,6 +1760,14 @@ keys = gnt_bindable_remap_keys(GNT_BINDABLE(wm), keys); idle_update = TRUE; + if(ignore_keys){ + if(keys && !strcmp(keys, "\033" GNT_KEY_CTRL_G)){ + if(gnt_bindable_perform_action_key(GNT_BINDABLE(wm), keys)){ + return TRUE; + } + } + return wm->cws->ordered ? gnt_widget_key_pressed(GNT_WIDGET(wm->cws->ordered->data), keys) : FALSE; + } if (gnt_bindable_perform_action_key(GNT_BINDABLE(wm), keys)) { return TRUE; @@ -1927,7 +1986,7 @@ if (!node) return; - + if (widget != wm->_list.window && !GNT_IS_MENU(widget) && wm->cws->ordered->data != widget) { GntWidget *w = wm->cws->ordered->data; @@ -2008,3 +2067,4 @@ wm->event_stack = set; } +
--- a/finch/libgnt/pygnt/example/rss/gntrss-ui.py Sat Aug 11 21:08:06 2007 +0000 +++ b/finch/libgnt/pygnt/example/rss/gntrss-ui.py Sat Aug 11 21:08:27 2007 +0000 @@ -73,6 +73,7 @@ if first: self.set_active(first) self.set_selected(first) + return True def __init__(self): self.active = None
--- a/finch/libgnt/pygnt/example/rss/gntrss.py Sat Aug 11 21:08:06 2007 +0000 +++ b/finch/libgnt/pygnt/example/rss/gntrss.py Sat Aug 11 21:08:27 2007 +0000 @@ -55,8 +55,7 @@ item['date'] = self.date = time.ctime() self.date_parsed = feedparser._parse_date(self.date) - self.title = item['title'] - sum = item['summary'] + self.title = item['title'].encode('utf8') self.summary = item['summary'].encode('utf8') self.link = item['link'] self.parent = parent @@ -79,7 +78,7 @@ gobject.type_register(FeedItem) def item_hash(item): - return str(item['date'] + item['title']) + return str(item['title']) """ The Feed class. It will update the 'link', 'title', 'desc' and 'items' @@ -171,6 +170,7 @@ self.set_property('unread', unread) for hv in tmp: + self.items.remove(tmp[hv]) tmp[hv].remove() "Also notify the UI about the count change"
--- a/finch/libgnt/wms/Makefile.am Sat Aug 11 21:08:06 2007 +0000 +++ b/finch/libgnt/wms/Makefile.am Sat Aug 11 21:08:27 2007 +0000 @@ -1,10 +1,16 @@ +if PURPLE_AVAILABLE +# These custom wms depend on libpurple +purple_wms = s.la irssi.la +else +purple_wms = +endif + s_la_LDFLAGS = -module -avoid-version irssi_la_LDFLAGS = -module -avoid-version plugin_LTLIBRARIES = \ - s.la \ - irssi.la - + $(purple_wms) + plugindir = $(libdir)/gnt irssi_la_SOURCES = irssi.c
--- a/finch/libgnt/wms/s.c Sat Aug 11 21:08:06 2007 +0000 +++ b/finch/libgnt/wms/s.c Sat Aug 11 21:08:27 2007 +0000 @@ -15,6 +15,10 @@ #define TYPE_S (s_get_gtype()) +#ifdef _S +#undef _S +#endif + typedef struct _S { GntWM inherit;
--- a/libpurple/account.h Sat Aug 11 21:08:06 2007 +0000 +++ b/libpurple/account.h Sat Aug 11 21:08:27 2007 +0000 @@ -88,8 +88,8 @@ const char *alias, const char *message, gboolean on_list, - GCallback authorize_cb, - GCallback deny_cb, + PurpleAccountRequestAuthorizationCb authorize_cb, + PurpleAccountRequestAuthorizationCb deny_cb, void *user_data); /** Close a pending request for authorization. \a ui_handle is a handle
--- a/libpurple/log.c Sat Aug 11 21:08:06 2007 +0000 +++ b/libpurple/log.c Sat Aug 11 21:08:27 2007 +0000 @@ -718,9 +718,9 @@ if (tmp < start) g_string_append_len(newmsg, tmp, start - tmp); - idstr = g_datalist_get_data(&attributes, "id"); + if ((idstr = g_datalist_get_data(&attributes, "id")) != NULL) + imgid = atoi(idstr); - imgid = atoi(idstr); if (imgid != 0) { FILE *image_file; @@ -735,6 +735,7 @@ if (image == NULL) { /* This should never happen. */ + /* This *does* happen for failed Direct-IMs -DAA */ g_string_free(newmsg, TRUE); g_return_val_if_reached((char *)msg); }
--- a/libpurple/prefs.c Sat Aug 11 21:08:06 2007 +0000 +++ b/libpurple/prefs.c Sat Aug 11 21:08:27 2007 +0000 @@ -1341,7 +1341,6 @@ list = g_list_append(list, g_strdup_printf("%s%s%s", name, sep, child->name)); } return list; - } void
--- a/libpurple/protocols/bonjour/bonjour.c Sat Aug 11 21:08:06 2007 +0000 +++ b/libpurple/protocols/bonjour/bonjour.c Sat Aug 11 21:08:27 2007 +0000 @@ -138,6 +138,8 @@ return; } + bonjour_dns_sd_update_buddy_icon(bd->dns_sd_data); + /* Create a group for bonjour buddies */ bonjour_group = purple_group_new(BONJOUR_GROUP_NAME); purple_blist_add_group(bonjour_group, NULL); @@ -283,17 +285,33 @@ bb->conversation = NULL; } +static +void bonjour_set_buddy_icon(PurpleConnection *conn, PurpleStoredImage *img) +{ + BonjourData *bd = conn->proto_data; + bonjour_dns_sd_update_buddy_icon(bd->dns_sd_data); +} + + static char * bonjour_status_text(PurpleBuddy *buddy) { - PurplePresence *presence; + const PurplePresence *presence; + const PurpleStatus *status; + const char *message; + gchar *ret = NULL; presence = purple_buddy_get_presence(buddy); + status = purple_presence_get_active_status(presence); - if (purple_presence_is_online(presence) && !purple_presence_is_available(presence)) - return g_strdup(_("Away")); + message = purple_status_get_attr_string(status, "message"); - return NULL; + if (message != NULL) { + ret = g_markup_escape_text(message, -1); + purple_util_chrreplace(ret, '\n', ' '); + } + + return ret; } static void @@ -301,6 +319,7 @@ { PurplePresence *presence; PurpleStatus *status; + BonjourBuddy *bb = buddy->proto_data; const char *status_description; const char *message; @@ -318,6 +337,23 @@ purple_notify_user_info_add_pair(user_info, _("Status"), status_description); if (message != NULL) purple_notify_user_info_add_pair(user_info, _("Message"), message); + + /* Only show first/last name if there is a nickname set (to avoid duplication) */ + if (bb->nick != NULL) { + if (bb->first != NULL) + purple_notify_user_info_add_pair(user_info, _("First name"), bb->first); + if (bb->first != NULL) + purple_notify_user_info_add_pair(user_info, _("Last name"), bb->last); + } + + if (bb->email != NULL) + purple_notify_user_info_add_pair(user_info, _("E-Mail"), bb->email); + + if (bb->AIM != NULL) + purple_notify_user_info_add_pair(user_info, _("AIM Account"), bb->AIM); + + if (bb->jid!= NULL) + purple_notify_user_info_add_pair(user_info, _("XMPP Account"), bb->jid); } static gboolean @@ -339,10 +375,9 @@ OPT_PROTO_NO_PASSWORD, NULL, /* user_splits */ NULL, /* protocol_options */ - /* {"png", 0, 0, 96, 96, 0, PURPLE_ICON_SCALE_DISPLAY}, */ /* icon_spec */ - NO_BUDDY_ICONS, /* not yet */ /* icon_spec */ + {"png,gif,jpeg", 0, 0, 96, 96, 65535, PURPLE_ICON_SCALE_DISPLAY}, /* icon_spec */ bonjour_list_icon, /* list_icon */ - NULL, /* list_emblem */ + NULL, /* list_emblem */ bonjour_status_text, /* status_text */ bonjour_tooltip_text, /* tooltip_text */ bonjour_status_types, /* status_types */ @@ -384,7 +419,7 @@ NULL, /* buddy_free */ bonjour_convo_closed, /* convo_closed */ NULL, /* normalize */ - NULL, /* set_buddy_icon */ + bonjour_set_buddy_icon, /* set_buddy_icon */ NULL, /* remove_group */ NULL, /* get_cb_real_name */ NULL, /* set_chat_topic */ @@ -412,11 +447,11 @@ PURPLE_PLUGIN_MAGIC, PURPLE_MAJOR_VERSION, PURPLE_MINOR_VERSION, - PURPLE_PLUGIN_PROTOCOL, /**< type */ + PURPLE_PLUGIN_PROTOCOL, /**< type */ NULL, /**< ui_requirement */ 0, /**< flags */ NULL, /**< dependencies */ - PURPLE_PRIORITY_DEFAULT, /**< priority */ + PURPLE_PRIORITY_DEFAULT, /**< priority */ "prpl-bonjour", /**< id */ "Bonjour", /**< name */ @@ -426,10 +461,10 @@ /** description */ N_("Bonjour Protocol Plugin"), NULL, /**< author */ - PURPLE_WEBSITE, /**< homepage */ + PURPLE_WEBSITE, /**< homepage */ NULL, /**< load */ - plugin_unload, /**< unload */ + plugin_unload, /**< unload */ NULL, /**< destroy */ NULL, /**< ui_info */ @@ -533,7 +568,7 @@ { default_firstname = g_strndup(fullname, splitpoint - fullname); tmp = &splitpoint[1]; - + /* The last name may be followed by a comma and additional data. * Only use the last name itself. */
--- a/libpurple/protocols/bonjour/buddy.c Sat Aug 11 21:08:06 2007 +0000 +++ b/libpurple/protocols/bonjour/buddy.c Sat Aug 11 21:08:27 2007 +0000 @@ -18,6 +18,7 @@ #include <stdlib.h> #include "internal.h" +#include "cipher.h" #include "buddy.h" #include "account.h" #include "blist.h" @@ -41,6 +42,26 @@ return buddy; } +#define _B_CLR(x) g_free(x); x = NULL; + +void clear_bonjour_buddy_values(BonjourBuddy *buddy) { + + _B_CLR(buddy->first) + _B_CLR(buddy->email); + _B_CLR(buddy->ext); + _B_CLR(buddy->jid); + _B_CLR(buddy->last); + _B_CLR(buddy->msg); + _B_CLR(buddy->nick); + _B_CLR(buddy->node); + _B_CLR(buddy->phsh); + _B_CLR(buddy->status); + _B_CLR(buddy->vc); + _B_CLR(buddy->ver); + _B_CLR(buddy->AIM); + +} + void set_bonjour_buddy_value(BonjourBuddy* buddy, const char *record_key, const char *value, uint32_t len){ gchar **fld = NULL; @@ -106,11 +127,10 @@ PurpleBuddy *buddy; PurpleGroup *group; PurpleAccount *account = bonjour_buddy->account; - const char *status_id, *first, *last, *old_hash, *new_hash; - gchar *alias = NULL; + const char *status_id, *old_hash, *new_hash; /* Translate between the Bonjour status and the Purple status */ - if (g_ascii_strcasecmp("dnd", bonjour_buddy->status) == 0) + if (bonjour_buddy->status != NULL && g_ascii_strcasecmp("dnd", bonjour_buddy->status) == 0) status_id = BONJOUR_STATUS_ID_AWAY; else status_id = BONJOUR_STATUS_ID_AVAILABLE; @@ -138,15 +158,21 @@ } /* Create the alias for the buddy using the first and the last name */ - first = bonjour_buddy->first; - last = bonjour_buddy->last; - if ((first && *first) || (last && *last)) - alias = g_strdup_printf("%s%s%s", - (first && *first ? first : ""), - (first && *first && last && *last ? " " : ""), - (last && *last ? last : "")); - serv_got_alias(purple_account_get_connection(account), buddy->name, alias); - g_free(alias); + if (bonjour_buddy->nick) + serv_got_alias(purple_account_get_connection(account), buddy->name, bonjour_buddy->nick); + else { + gchar *alias = NULL; + const char *first, *last; + first = bonjour_buddy->first; + last = bonjour_buddy->last; + if ((first && *first) || (last && *last)) + alias = g_strdup_printf("%s%s%s", + (first && *first ? first : ""), + (first && *first && last && *last ? " " : ""), + (last && *last ? last : "")); + serv_got_alias(purple_account_get_connection(account), buddy->name, alias); + g_free(alias); + } /* Set the user's status */ if (bonjour_buddy->msg != NULL) @@ -166,12 +192,46 @@ new_hash = (bonjour_buddy->phsh && *(bonjour_buddy->phsh)) ? bonjour_buddy->phsh : NULL; if (new_hash && (!old_hash || strcmp(old_hash, new_hash) != 0)) { /* Look up the new icon data */ + /* TODO: Make sure the hash assigned to the retrieved buddy icon is the same + * as what we looked up. */ bonjour_dns_sd_retrieve_buddy_icon(bonjour_buddy); - } else + } else if (!new_hash) purple_buddy_icons_set_for_user(account, buddy->name, NULL, 0, NULL); } /** + * We got the buddy icon data; deal with it + */ +void bonjour_buddy_got_buddy_icon(BonjourBuddy *buddy, gconstpointer data, gsize len) { + /* Recalculate the hash instead of using the current phsh to make sure it is accurate for the icon. */ + int i; + gchar *enc; + char *p, hash[41]; + unsigned char hashval[20]; + + if (data == NULL || len == 0) + return; + + enc = purple_base64_encode(data, len); + + purple_cipher_digest_region("sha1", data, + len, sizeof(hashval), + hashval, NULL); + + p = hash; + for(i=0; i<20; i++, p+=2) + snprintf(p, 3, "%02x", hashval[i]); + + purple_debug_info("bonjour", "Got buddy icon for %s icon hash='%s' phsh='%s'.\n", buddy->name, + hash, buddy->phsh ? buddy->phsh : "(null)"); + + purple_buddy_icons_set_for_user(buddy->account, buddy->name, + g_memdup(data, len), len, hash); + + g_free(enc); +} + +/** * Deletes a buddy from memory. */ void @@ -179,8 +239,6 @@ { g_free(buddy->name); g_free(buddy->ip); - g_free(buddy->full_service_name); - g_free(buddy->first); g_free(buddy->phsh); g_free(buddy->status);
--- a/libpurple/protocols/bonjour/buddy.h Sat Aug 11 21:08:06 2007 +0000 +++ b/libpurple/protocols/bonjour/buddy.h Sat Aug 11 21:08:27 2007 +0000 @@ -29,7 +29,6 @@ gchar *name; /* TODO: Remove and just use the hostname */ gchar *ip; - gchar *full_service_name; gint port_p2pj; gchar *first; @@ -76,9 +75,15 @@ BonjourBuddy *bonjour_buddy_new(const gchar *name, PurpleAccount *account); /** + * Clear any existing values from the buddy. + * This is called before updating so that we can notice removals + */ +void clear_bonjour_buddy_values(BonjourBuddy *buddy); + +/** * Sets a value in the BonjourBuddy struct, destroying the old value */ -void set_bonjour_buddy_value(BonjourBuddy* buddy, const char *record_key, const char *value, uint32_t len); +void set_bonjour_buddy_value(BonjourBuddy *buddy, const char *record_key, const char *value, uint32_t len); /** * Check if all the compulsory buddy data is present. @@ -91,6 +96,11 @@ void bonjour_buddy_add_to_purple(BonjourBuddy *buddy); /** + * We got the buddy icon data; deal with it + */ +void bonjour_buddy_got_buddy_icon(BonjourBuddy *buddy, gconstpointer data, gsize len); + +/** * Deletes a buddy from memory. */ void bonjour_buddy_delete(BonjourBuddy *buddy);
--- a/libpurple/protocols/bonjour/issues.txt Sat Aug 11 21:08:06 2007 +0000 +++ b/libpurple/protocols/bonjour/issues.txt Sat Aug 11 21:08:27 2007 +0000 @@ -3,6 +3,5 @@ ========================================== * Status changes don't work -* Avatars * File transfers * Typing notifications
--- a/libpurple/protocols/bonjour/mdns_avahi.c Sat Aug 11 21:08:06 2007 +0000 +++ b/libpurple/protocols/bonjour/mdns_avahi.c Sat Aug 11 21:08:27 2007 +0000 @@ -19,6 +19,7 @@ #include "mdns_interface.h" #include "debug.h" #include "buddy.h" +#include "bonjour.h" #include <avahi-client/client.h> #include <avahi-client/lookup.h> @@ -32,14 +33,25 @@ #include <avahi-glib/glib-malloc.h> #include <avahi-glib/glib-watch.h> +/* For some reason, this is missing from the Avahi type defines */ +#ifndef AVAHI_DNS_TYPE_NULL +#define AVAHI_DNS_TYPE_NULL 0x0A +#endif + /* data used by avahi bonjour implementation */ typedef struct _avahi_session_impl_data { AvahiClient *client; AvahiGLibPoll *glib_poll; AvahiServiceBrowser *sb; AvahiEntryGroup *group; + AvahiEntryGroup *buddy_icon_group; } AvahiSessionImplData; +typedef struct _avahi_buddy_impl_data { + AvahiServiceResolver *resolver; + AvahiRecordBrowser *buddy_icon_rec_browser; +} AvahiBuddyImplData; + static void _resolver_callback(AvahiServiceResolver *r, AvahiIfIndex interface, AvahiProtocol protocol, AvahiResolverEvent event, const char *name, const char *type, const char *domain, @@ -59,11 +71,14 @@ case AVAHI_RESOLVER_FAILURE: purple_debug_error("bonjour", "_resolve_callback - Failure: %s\n", avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(r)))); + avahi_service_resolver_free(r); break; case AVAHI_RESOLVER_FOUND: /* create a buddy record */ buddy = bonjour_buddy_new(name, account); + ((AvahiBuddyImplData *)buddy->mdns_impl_data)->resolver = r; + /* Get the ip as a string */ buddy->ip = g_malloc(AVAHI_ADDRESS_STR_MAX); avahi_address_snprint(buddy->ip, AVAHI_ADDRESS_STR_MAX, a); @@ -71,6 +86,7 @@ buddy->port_p2pj = port; /* Obtain the parameters from the text_record */ + clear_bonjour_buddy_values(buddy); l = txt; while (l != NULL) { ret = avahi_string_list_get_pair(l, &key, &value, &size); @@ -95,7 +111,6 @@ purple_debug_info("bonjour", "Unrecognized Service Resolver event: %d.\n", event); } - avahi_service_resolver_free(r); } static void @@ -145,6 +160,30 @@ } static void +_buddy_icon_group_cb(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) { + BonjourDnsSd *data = userdata; + AvahiSessionImplData *idata = data->mdns_impl_data; + + g_return_if_fail(g == idata->buddy_icon_group || idata->buddy_icon_group == NULL); + + switch(state) { + case AVAHI_ENTRY_GROUP_ESTABLISHED: + purple_debug_info("bonjour", "Successfully registered buddy icon data.\n"); + case AVAHI_ENTRY_GROUP_COLLISION: + purple_debug_error("bonjour", "Collision registering buddy icon data.\n"); + break; + case AVAHI_ENTRY_GROUP_FAILURE: + purple_debug_error("bonjour", "Error registering buddy icon data: %s\n.", + avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g)))); + break; + case AVAHI_ENTRY_GROUP_UNCOMMITED: + case AVAHI_ENTRY_GROUP_REGISTERING: + break; + } + +} + +static void _entry_group_cb(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) { AvahiSessionImplData *idata = userdata; @@ -170,6 +209,31 @@ } +static void +_buddy_icon_record_cb(AvahiRecordBrowser *b, AvahiIfIndex interface, AvahiProtocol protocol, + AvahiBrowserEvent event, const char *name, uint16_t clazz, uint16_t type, + const void *rdata, size_t size, AvahiLookupResultFlags flags, void *userdata) { + BonjourBuddy *buddy = userdata; + AvahiBuddyImplData *idata = buddy->mdns_impl_data; + + switch (event) { + case AVAHI_BROWSER_NEW: + bonjour_buddy_got_buddy_icon(buddy, rdata, size); + break; + case AVAHI_BROWSER_REMOVE: + case AVAHI_BROWSER_CACHE_EXHAUSTED: + case AVAHI_BROWSER_ALL_FOR_NOW: + case AVAHI_BROWSER_FAILURE: + purple_debug_error("bonjour", "Error rerieving buddy icon record: %s\n", + avahi_strerror(avahi_client_errno(avahi_record_browser_get_client(b)))); + break; + } + + /* Stop listening */ + avahi_record_browser_free(idata->buddy_icon_rec_browser); + idata->buddy_icon_rec_browser = NULL; +} + /**************************** * mdns_interface functions * ****************************/ @@ -203,10 +267,8 @@ return TRUE; } -gboolean _mdns_publish(BonjourDnsSd *data, PublishType type) { +gboolean _mdns_publish(BonjourDnsSd *data, PublishType type, GSList *records) { int publish_result = 0; - char portstring[6]; - const char *jid, *aim, *email; AvahiSessionImplData *idata = data->mdns_impl_data; AvahiStringList *lst = NULL; @@ -223,44 +285,11 @@ } } - /* Convert the port to a string */ - snprintf(portstring, sizeof(portstring), "%d", data->port_p2pj); - - jid = purple_account_get_string(data->account, "jid", NULL); - aim = purple_account_get_string(data->account, "AIM", NULL); - email = purple_account_get_string(data->account, "email", NULL); - - /* We should try to follow XEP-0174, but some clients have "issues", so we humor them. - * See http://telepathy.freedesktop.org/wiki/SalutInteroperability - */ - - /* Needed by iChat */ - lst = avahi_string_list_add_pair(lst,"txtvers", "1"); - /* Needed by Gaim/Pidgin <= 2.0.1 (remove at some point) */ - lst = avahi_string_list_add_pair(lst, "1st", data->first); - /* Needed by Gaim/Pidgin <= 2.0.1 (remove at some point) */ - lst = avahi_string_list_add_pair(lst, "last", data->last); - /* Needed by Adium */ - lst = avahi_string_list_add_pair(lst, "port.p2pj", portstring); - /* Needed by iChat, Gaim/Pidgin <= 2.0.1 */ - lst = avahi_string_list_add_pair(lst, "status", data->status); - /* Currently always set to "!" since we don't support AV and wont ever be in a conference */ - lst = avahi_string_list_add_pair(lst, "vc", data->vc); - lst = avahi_string_list_add_pair(lst, "ver", VERSION); - if (email != NULL && *email != '\0') - lst = avahi_string_list_add_pair(lst, "email", email); - if (jid != NULL && *jid != '\0') - lst = avahi_string_list_add_pair(lst, "jid", jid); - /* Nonstandard, but used by iChat */ - if (aim != NULL && *aim != '\0') - lst = avahi_string_list_add_pair(lst, "AIM", aim); - if (data->msg != NULL && *data->msg != '\0') - lst = avahi_string_list_add_pair(lst, "msg", data->msg); - if (data->phsh != NULL && *data->phsh != '\0') - lst = avahi_string_list_add_pair(lst, "phsh", data->phsh); - - /* TODO: ext, nick, node */ - + while (records) { + PurpleKeyValuePair *kvp = records->data; + lst = avahi_string_list_add_pair(lst, kvp->key, kvp->value); + records = records->next; + } /* Publish the service */ switch (type) { @@ -290,7 +319,8 @@ return FALSE; } - if ((publish_result = avahi_entry_group_commit(idata->group)) < 0) { + if (type == PUBLISH_START + && (publish_result = avahi_entry_group_commit(idata->group)) < 0) { purple_debug_error("bonjour", "Failed to commit " ICHAT_SERVICE " service. Error: %s\n", avahi_strerror(publish_result)); @@ -317,9 +347,71 @@ return TRUE; } -/* This is done differently than with Howl/Apple Bonjour */ -guint _mdns_register_to_mainloop(BonjourDnsSd *data) { - return 0; +gboolean _mdns_set_buddy_icon_data(BonjourDnsSd *data, gconstpointer avatar_data, gsize avatar_len) { + AvahiSessionImplData *idata = data->mdns_impl_data; + + if (idata == NULL || idata->client == NULL) + return FALSE; + + if (avatar_data != NULL) { + gboolean new_group = FALSE; + gchar *svc_name; + int ret; + AvahiPublishFlags flags = 0; + + if (idata->buddy_icon_group == NULL) { + purple_debug_info("bonjour", "Setting new buddy icon.\n"); + new_group = TRUE; + + idata->buddy_icon_group = avahi_entry_group_new(idata->client, + _buddy_icon_group_cb, data); + } else { + purple_debug_info("bonjour", "Updating existing buddy icon.\n"); + flags |= AVAHI_PUBLISH_UPDATE; + } + + if (idata->buddy_icon_group == NULL) { + purple_debug_error("bonjour", + "Unable to initialize the buddy icon group (%s).\n", + avahi_strerror(avahi_client_errno(idata->client))); + return FALSE; + } + + svc_name = g_strdup_printf("%s." ICHAT_SERVICE "local", + purple_account_get_username(data->account)); + + ret = avahi_entry_group_add_record(idata->buddy_icon_group, AVAHI_IF_UNSPEC, + AVAHI_PROTO_UNSPEC, flags, svc_name, + AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_NULL, 120, avatar_data, avatar_len); + + g_free(svc_name); + + if (ret < 0) { + purple_debug_error("bonjour", + "Failed to register buddy icon. Error: %s\n", avahi_strerror(ret)); + if (new_group) { + avahi_entry_group_free(idata->buddy_icon_group); + idata->buddy_icon_group = NULL; + } + return FALSE; + } + + if (new_group && (ret = avahi_entry_group_commit(idata->buddy_icon_group)) < 0) { + purple_debug_error("bonjour", + "Failed to commit buddy icon group. Error: %s\n", avahi_strerror(ret)); + if (new_group) { + avahi_entry_group_free(idata->buddy_icon_group); + idata->buddy_icon_group = NULL; + } + return FALSE; + } + } else if (idata->buddy_icon_group != NULL) { + purple_debug_info("bonjour", "Removing existing buddy icon.\n"); + avahi_entry_group_free(idata->buddy_icon_group); + idata->buddy_icon_group = NULL; + } + + return TRUE; } void _mdns_stop(BonjourDnsSd *data) { @@ -340,10 +432,50 @@ } void _mdns_init_buddy(BonjourBuddy *buddy) { + buddy->mdns_impl_data = g_new0(AvahiBuddyImplData, 1); } void _mdns_delete_buddy(BonjourBuddy *buddy) { + AvahiBuddyImplData *idata = buddy->mdns_impl_data; + + g_return_if_fail(idata != NULL); + + if (idata->buddy_icon_rec_browser != NULL) + avahi_record_browser_free(idata->buddy_icon_rec_browser); + + if (idata->resolver != NULL) + avahi_service_resolver_free(idata->resolver); + + g_free(idata); + + buddy->mdns_impl_data = NULL; } -void bonjour_dns_sd_retrieve_buddy_icon(BonjourBuddy* buddy) { +void _mdns_retrieve_buddy_icon(BonjourBuddy* buddy) { + PurpleConnection *conn = purple_account_get_connection(buddy->account); + BonjourData *bd = conn->proto_data; + AvahiSessionImplData *session_idata = bd->dns_sd_data->mdns_impl_data; + AvahiBuddyImplData *idata = buddy->mdns_impl_data; + gchar *name; + + g_return_if_fail(idata != NULL); + + if (idata->buddy_icon_rec_browser != NULL) + avahi_record_browser_free(idata->buddy_icon_rec_browser); + + purple_debug_info("bonjour", "Retrieving buddy icon for '%s'.\n", buddy->name); + + name = g_strdup_printf("%s." ICHAT_SERVICE "local", buddy->name); + idata->buddy_icon_rec_browser = avahi_record_browser_new(session_idata->client, AVAHI_IF_UNSPEC, + AVAHI_PROTO_UNSPEC, name, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_NULL, 0, + _buddy_icon_record_cb, buddy); + g_free(name); + + if (!idata->buddy_icon_rec_browser) { + purple_debug_error("bonjour", + "Unable to initialize buddy icon record browser. Error: %s\n.", + avahi_strerror(avahi_client_errno(session_idata->client))); + } + } +
--- a/libpurple/protocols/bonjour/mdns_common.c Sat Aug 11 21:08:06 2007 +0000 +++ b/libpurple/protocols/bonjour/mdns_common.c Sat Aug 11 21:08:27 2007 +0000 @@ -17,30 +17,27 @@ #include <string.h> #include "internal.h" +#include "cipher.h" +#include "debug.h" + #include "mdns_common.h" #include "mdns_interface.h" #include "bonjour.h" #include "buddy.h" -#include "debug.h" /** * Allocate space for the dns-sd data. */ -BonjourDnsSd * -bonjour_dns_sd_new() -{ +BonjourDnsSd * bonjour_dns_sd_new() { BonjourDnsSd *data = g_new0(BonjourDnsSd, 1); - return data; } /** * Deallocate the space of the dns-sd data. */ -void -bonjour_dns_sd_free(BonjourDnsSd *data) -{ +void bonjour_dns_sd_free(BonjourDnsSd *data) { g_free(data->first); g_free(data->last); g_free(data->phsh); @@ -50,12 +47,90 @@ g_free(data); } +static GSList *generate_presence_txt_records(BonjourDnsSd *data) { + GSList *ret = NULL; + PurpleKeyValuePair *kvp; + char portstring[6]; + const char *jid, *aim, *email; + + /* Convert the port to a string */ + snprintf(portstring, sizeof(portstring), "%d", data->port_p2pj); + + jid = purple_account_get_string(data->account, "jid", NULL); + aim = purple_account_get_string(data->account, "AIM", NULL); + email = purple_account_get_string(data->account, "email", NULL); + +#define _M_ADD_R(k, v) \ + kvp = g_new0(PurpleKeyValuePair, 1); \ + kvp->key = g_strdup(k); \ + kvp->value = g_strdup(v); \ + ret = g_slist_prepend(ret, kvp); \ + + /* We should try to follow XEP-0174, but some clients have "issues", so we humor them. + * See http://telepathy.freedesktop.org/wiki/SalutInteroperability + */ + + /* Needed by iChat */ + _M_ADD_R("txtvers", "1") + /* Needed by Gaim/Pidgin <= 2.0.1 (remove at some point) */ + _M_ADD_R("1st", data->first) + /* Needed by Gaim/Pidgin <= 2.0.1 (remove at some point) */ + _M_ADD_R("last", data->last) + /* Needed by Adium */ + _M_ADD_R("port.p2pj", portstring) + /* Needed by iChat, Gaim/Pidgin <= 2.0.1 */ + _M_ADD_R("status", data->status) + _M_ADD_R("node", "libpurple") + _M_ADD_R("ver", VERSION) + /* Currently always set to "!" since we don't support AV and wont ever be in a conference */ + _M_ADD_R("vc", data->vc) + if (email != NULL && *email != '\0') { + _M_ADD_R("email", email) + } + if (jid != NULL && *jid != '\0') { + _M_ADD_R("jid", jid) + } + /* Nonstandard, but used by iChat */ + if (aim != NULL && *aim != '\0') { + _M_ADD_R("AIM", aim) + } + if (data->msg != NULL && *data->msg != '\0') { + _M_ADD_R("msg", data->msg) + } + if (data->phsh != NULL && *data->phsh != '\0') { + _M_ADD_R("phsh", data->phsh) + } + + /* TODO: ext, nick */ + return ret; +} + +static void free_presence_txt_records(GSList *lst) { + PurpleKeyValuePair *kvp; + while(lst) { + kvp = lst->data; + g_free(kvp->key); + g_free(kvp->value); + g_free(kvp); + lst = g_slist_remove(lst, lst->data); + } +} + +static gboolean publish_presence(BonjourDnsSd *data, PublishType type) { + GSList *txt_records; + gboolean ret; + + txt_records = generate_presence_txt_records(data); + ret = _mdns_publish(data, type, txt_records); + free_presence_txt_records(txt_records); + + return ret; +} + /** * Send a new dns-sd packet updating our status. */ -void -bonjour_dns_sd_send_status(BonjourDnsSd *data, const char *status, const char *status_message) -{ +void bonjour_dns_sd_send_status(BonjourDnsSd *data, const char *status, const char *status_message) { g_free(data->status); g_free(data->msg); @@ -63,26 +138,78 @@ data->msg = g_strdup(status_message); /* Update our text record with the new status */ - _mdns_publish(data, PUBLISH_UPDATE); /* <--We must control the errors */ + publish_presence(data, PUBLISH_UPDATE); +} + +/** + * Retrieve the buddy icon blob + */ +void bonjour_dns_sd_retrieve_buddy_icon(BonjourBuddy* buddy) { + _mdns_retrieve_buddy_icon(buddy); +} + +void bonjour_dns_sd_update_buddy_icon(BonjourDnsSd *data) { + PurpleStoredImage *img; + + if ((img = purple_buddy_icons_find_account_icon(data->account))) { + gconstpointer avatar_data; + gsize avatar_len; + + avatar_data = purple_imgstore_get_data(img); + avatar_len = purple_imgstore_get_size(img); + + if (_mdns_set_buddy_icon_data(data, avatar_data, avatar_len)) { + int i; + gchar *enc; + char *p, hash[41]; + unsigned char hashval[20]; + + enc = purple_base64_encode(avatar_data, avatar_len); + + purple_cipher_digest_region("sha1", avatar_data, + avatar_len, sizeof(hashval), + hashval, NULL); + + p = hash; + for(i=0; i<20; i++, p+=2) + snprintf(p, 3, "%02x", hashval[i]); + + g_free(data->phsh); + data->phsh = g_strdup(hash); + + g_free(enc); + + /* Update our TXT record */ + publish_presence(data, PUBLISH_UPDATE); + } + + purple_imgstore_unref(img); + } else { + /* We need to do this regardless of whether data->phsh is set so that we + * cancel any icons that are currently in the process of being set */ + _mdns_set_buddy_icon_data(data, NULL, 0); + if (data->phsh != NULL) { + /* Clear the buddy icon */ + g_free(data->phsh); + data->phsh = NULL; + /* Update our TXT record */ + publish_presence(data, PUBLISH_UPDATE); + } + } } /** * Advertise our presence within the dns-sd daemon and start browsing * for other bonjour peers. */ -gboolean -bonjour_dns_sd_start(BonjourDnsSd *data) -{ - PurpleConnection *gc; - - gc = purple_account_get_connection(data->account); +gboolean bonjour_dns_sd_start(BonjourDnsSd *data) { /* Initialize the dns-sd data and session */ if (!_mdns_init_session(data)) return FALSE; /* Publish our bonjour IM client at the mDNS daemon */ - if (!_mdns_publish(data, PUBLISH_START)) + if (!publish_presence(data, PUBLISH_START)) return FALSE; /* Advise the daemon that we are waiting for connections */ @@ -91,11 +218,6 @@ return FALSE; } - - /* Get the socket that communicates with the mDNS daemon and bind it to a */ - /* callback that will handle the dns_sd packets */ - gc->inpa = _mdns_register_to_mainloop(data); - return TRUE; } @@ -103,14 +225,6 @@ * Unregister the "_presence._tcp" service at the mDNS daemon. */ -void -bonjour_dns_sd_stop(BonjourDnsSd *data) -{ - PurpleConnection *gc; - +void bonjour_dns_sd_stop(BonjourDnsSd *data) { _mdns_stop(data); - - gc = purple_account_get_connection(data->account); - if (gc->inpa > 0) - purple_input_remove(gc->inpa); }
--- a/libpurple/protocols/bonjour/mdns_common.h Sat Aug 11 21:08:06 2007 +0000 +++ b/libpurple/protocols/bonjour/mdns_common.h Sat Aug 11 21:08:27 2007 +0000 @@ -42,6 +42,11 @@ void bonjour_dns_sd_retrieve_buddy_icon(BonjourBuddy* buddy); /** + * Deal with a buddy icon update + */ +void bonjour_dns_sd_update_buddy_icon(BonjourDnsSd *data); + +/** * Advertise our presence within the dns-sd daemon and start * browsing for other bonjour peers. */
--- a/libpurple/protocols/bonjour/mdns_howl.c Sat Aug 11 21:08:06 2007 +0000 +++ b/libpurple/protocols/bonjour/mdns_howl.c Sat Aug 11 21:08:27 2007 +0000 @@ -26,6 +26,7 @@ typedef struct _howl_impl_data { sw_discovery session; sw_discovery_oid session_id; + guint session_handler; } HowlSessionImplData; static sw_result HOWL_API @@ -84,6 +85,7 @@ /* Obtain the parameters from the text_record */ if ((text_record_len > 0) && (text_record) && (*text_record != '\0')) { + clear_bonjour_buddy_values(buddy); sw_text_record_iterator_init(&iterator, text_record, text_record_len); while (sw_text_record_iterator_next(iterator, key, (sw_octet *)value, &value_length) == SW_OKAY) set_bonjour_buddy_value(buddy, key, value, value_length); @@ -192,11 +194,9 @@ } -gboolean _mdns_publish(BonjourDnsSd *data, PublishType type) { +gboolean _mdns_publish(BonjourDnsSd *data, PublishType type, GSList *records) { sw_text_record dns_data; sw_result publish_result = SW_OKAY; - char portstring[6]; - const char *jid, *aim, *email; HowlSessionImplData *idata = data->mdns_impl_data; g_return_val_if_fail(idata != NULL, FALSE); @@ -207,43 +207,11 @@ return FALSE; } - /* Convert the port to a string */ - snprintf(portstring, sizeof(portstring), "%d", data->port_p2pj); - - jid = purple_account_get_string(data->account, "jid", NULL); - aim = purple_account_get_string(data->account, "AIM", NULL); - email = purple_account_get_string(data->account, "email", NULL); - - /* We should try to follow XEP-0174, but some clients have "issues", so we humor them. - * See http://telepathy.freedesktop.org/wiki/SalutInteroperability - */ - - /* Needed by iChat */ - sw_text_record_add_key_and_string_value(dns_data, "txtvers", "1"); - /* Needed by Gaim/Pidgin <= 2.0.1 (remove at some point) */ - sw_text_record_add_key_and_string_value(dns_data, "1st", data->first); - /* Needed by Gaim/Pidgin <= 2.0.1 (remove at some point) */ - sw_text_record_add_key_and_string_value(dns_data, "last", data->last); - /* Needed by Adium */ - sw_text_record_add_key_and_string_value(dns_data, "port.p2pj", portstring); - /* Needed by iChat, Gaim/Pidgin <= 2.0.1 */ - sw_text_record_add_key_and_string_value(dns_data, "status", data->status); - /* Currently always set to "!" since we don't support AV and wont ever be in a conference */ - sw_text_record_add_key_and_string_value(dns_data, "vc", data->vc); - sw_text_record_add_key_and_string_value(dns_data, "ver", VERSION); - if (email != NULL && *email != '\0') - sw_text_record_add_key_and_string_value(dns_data, "email", email); - if (jid != NULL && *jid != '\0') - sw_text_record_add_key_and_string_value(dns_data, "jid", jid); - /* Nonstandard, but used by iChat */ - if (aim != NULL && *aim != '\0') - sw_text_record_add_key_and_string_value(dns_data, "AIM", aim); - if (data->msg != NULL && *data->msg != '\0') - sw_text_record_add_key_and_string_value(dns_data, "msg", data->msg); - if (data->phsh != NULL && *data->phsh != '\0') - sw_text_record_add_key_and_string_value(dns_data, "phsh", data->phsh); - - /* TODO: ext, nick, node */ + while (records) { + PurpleKeyValuePair *kvp = records->data; + sw_text_record_add_key_and_string_value(dns_data, kvp->key, kvp->value); + records = records->next; + } /* Publish the service */ switch (type) { @@ -276,17 +244,18 @@ g_return_val_if_fail(idata != NULL, FALSE); - return (sw_discovery_browse(idata->session, 0, ICHAT_SERVICE, NULL, _browser_reply, - data->account, &session_id) == SW_OKAY); + if (sw_discovery_browse(idata->session, 0, ICHAT_SERVICE, NULL, _browser_reply, + data->account, &session_id) == SW_OKAY) { + idata->session_handler = purple_input_add(sw_discovery_socket(idata->session), + PURPLE_INPUT_READ, _mdns_handle_event, idata->session); + return TRUE; + } + + return FALSE; } -guint _mdns_register_to_mainloop(BonjourDnsSd *data) { - HowlSessionImplData *idata = data->mdns_impl_data; - - g_return_val_if_fail(idata != NULL, 0); - - return purple_input_add(sw_discovery_socket(idata->session), - PURPLE_INPUT_READ, _mdns_handle_event, idata->session); +gboolean _mdns_set_buddy_icon_data(BonjourDnsSd *data, gconstpointer avatar_data, gsize avatar_len) { + return FALSE; } void _mdns_stop(BonjourDnsSd *data) { @@ -297,6 +266,8 @@ sw_discovery_cancel(idata->session, idata->session_id); + purple_input_remove(idata->session_handler); + /* TODO: should this really be g_free()'d ??? */ g_free(idata->session); @@ -311,5 +282,7 @@ void _mdns_delete_buddy(BonjourBuddy *buddy) { } -void bonjour_dns_sd_retrieve_buddy_icon(BonjourBuddy* buddy) { +void _mdns_retrieve_buddy_icon(BonjourBuddy* buddy) { } + +
--- a/libpurple/protocols/bonjour/mdns_interface.h Sat Aug 11 21:08:06 2007 +0000 +++ b/libpurple/protocols/bonjour/mdns_interface.h Sat Aug 11 21:08:27 2007 +0000 @@ -22,19 +22,18 @@ gboolean _mdns_init_session(BonjourDnsSd *data); -gboolean _mdns_publish(BonjourDnsSd *data, PublishType type); +gboolean _mdns_publish(BonjourDnsSd *data, PublishType type, GSList *records); gboolean _mdns_browse(BonjourDnsSd *data); -guint _mdns_register_to_mainloop(BonjourDnsSd *data); +void _mdns_stop(BonjourDnsSd *data); -void _mdns_stop(BonjourDnsSd *data); +gboolean _mdns_set_buddy_icon_data(BonjourDnsSd *data, gconstpointer avatar_data, gsize avatar_len); void _mdns_init_buddy(BonjourBuddy *buddy); void _mdns_delete_buddy(BonjourBuddy *buddy); -/* This doesn't quite belong here, but there really isn't any shared functionality */ -void bonjour_dns_sd_retrieve_buddy_icon(BonjourBuddy* buddy); +void _mdns_retrieve_buddy_icon(BonjourBuddy* buddy); #endif
--- a/libpurple/protocols/bonjour/mdns_win32.c Sat Aug 11 21:08:06 2007 +0000 +++ b/libpurple/protocols/bonjour/mdns_win32.c Sat Aug 11 21:08:27 2007 +0000 @@ -21,12 +21,14 @@ #include "mdns_interface.h" #include "dns_sd_proxy.h" #include "dnsquery.h" +#include "mdns_common.h" /* data structure for the resolve callback */ typedef struct _ResolveCallbackArgs { DNSServiceRef resolver; guint resolver_handler; + gchar *full_service_name; PurpleDnsQueryData *query; @@ -35,10 +37,12 @@ /* data used by win32 bonjour implementation */ typedef struct _win32_session_impl_data { - DNSServiceRef advertisement; - DNSServiceRef browser; + DNSServiceRef presence_svc; + DNSServiceRef browser_svc; + DNSRecordRef buddy_icon_rec; - guint advertisement_handler; /* hack... windows bonjour is broken, so we have to have this */ + guint presence_handler; + guint browser_handler; } Win32SessionImplData; typedef struct _win32_buddy_impl_data { @@ -60,6 +64,7 @@ uint8_t txt_len; int i; + clear_bonjour_buddy_values(buddy); for (i = 0; buddy_TXT_records[i] != NULL; i++) { txt_entry = TXTRecordGetValuePtr(record_len, record, buddy_TXT_records[i], &txt_len); if (txt_entry != NULL) @@ -68,7 +73,7 @@ } static void DNSSD_API -_mdns_text_record_query_callback(DNSServiceRef DNSServiceRef, DNSServiceFlags flags, +_mdns_record_query_callback(DNSServiceRef DNSServiceRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char *fullname, uint16_t rrtype, uint16_t rrclass, uint16_t rdlen, const void *rdata, uint32_t ttl, void *context) @@ -90,8 +95,7 @@ g_return_if_fail(idata != NULL); - purple_buddy_icons_set_for_user(buddy->account, buddy->name, - g_memdup(rdata, rdlen), rdlen, buddy->phsh); + bonjour_buddy_got_buddy_icon(buddy, rdata, rdlen); /* We've got what we need; stop listening */ purple_input_remove(idata->null_query_handler); @@ -120,14 +124,16 @@ /* finally, set up the continuous txt record watcher, and add the buddy to purple */ - if (kDNSServiceErr_NoError == DNSServiceQueryRecord(&idata->txt_query, 0, 0, buddy->full_service_name, - kDNSServiceType_TXT, kDNSServiceClass_IN, _mdns_text_record_query_callback, buddy)) { - int fd = DNSServiceRefSockFD(idata->txt_query); - idata->txt_query_handler = purple_input_add(fd, PURPLE_INPUT_READ, _mdns_handle_event, idata->txt_query); + if (kDNSServiceErr_NoError == DNSServiceQueryRecord(&idata->txt_query, kDNSServiceFlagsLongLivedQuery, + kDNSServiceInterfaceIndexAny, args->full_service_name, kDNSServiceType_TXT, + kDNSServiceClass_IN, _mdns_record_query_callback, buddy)) { + + purple_debug_info("bonjour", "Found buddy %s at %s:%d\n", buddy->name, buddy->ip, buddy->port_p2pj); + + idata->txt_query_handler = purple_input_add(DNSServiceRefSockFD(idata->txt_query), + PURPLE_INPUT_READ, _mdns_handle_event, idata->txt_query); bonjour_buddy_add_to_purple(buddy); - - purple_debug_info("bonjour", "Found buddy %s at %s:%d\n", buddy->name, buddy->ip, buddy->port_p2pj); } else bonjour_buddy_delete(buddy); @@ -138,6 +144,7 @@ /* free the remaining args memory */ purple_dnsquery_destroy(args->query); + g_free(args->full_service_name); g_free(args); } @@ -165,13 +172,14 @@ _mdns_parse_text_record(args->buddy, txtRecord, txtLen); /* set more arguments, and start the host resolver */ - args->buddy->full_service_name = g_strdup(fullname); + args->full_service_name = g_strdup(fullname); if (!(args->query = purple_dnsquery_a(hosttarget, port, _mdns_resolve_host_callback, args))) { purple_debug_error("bonjour", "service resolver - host resolution failed.\n"); bonjour_buddy_delete(args->buddy); + g_free(args->full_service_name); g_free(args); } } @@ -180,11 +188,11 @@ static void DNSSD_API _mdns_service_register_callback(DNSServiceRef sdRef, DNSServiceFlags flags, DNSServiceErrorType errorCode, - const char *name, const char *regtype, const char *domain, void *context) -{ - /* we don't actually care about anything said in this callback - this is only here because Bonjour for windows is broken */ + const char *name, const char *regtype, const char *domain, void *context) { + + /* TODO: deal with collision */ if (kDNSServiceErr_NoError != errorCode) - purple_debug_error("bonjour", "service advertisement - callback error.\n"); + purple_debug_error("bonjour", "service advertisement - callback error (%d).\n", errorCode); else purple_debug_info("bonjour", "service advertisement - callback.\n"); } @@ -235,61 +243,23 @@ return TRUE; } -gboolean _mdns_publish(BonjourDnsSd *data, PublishType type) { +gboolean _mdns_publish(BonjourDnsSd *data, PublishType type, GSList *records) { TXTRecordRef dns_data; - char portstring[6]; gboolean ret = TRUE; - const char *jid, *aim, *email; - DNSServiceErrorType set_ret; + DNSServiceErrorType set_ret = kDNSServiceErr_NoError; Win32SessionImplData *idata = data->mdns_impl_data; g_return_val_if_fail(idata != NULL, FALSE); TXTRecordCreate(&dns_data, 256, NULL); - /* Convert the port to a string */ - snprintf(portstring, sizeof(portstring), "%d", data->port_p2pj); - - jid = purple_account_get_string(data->account, "jid", NULL); - aim = purple_account_get_string(data->account, "AIM", NULL); - email = purple_account_get_string(data->account, "email", NULL); - - /* We should try to follow XEP-0174, but some clients have "issues", so we humor them. - * See http://telepathy.freedesktop.org/wiki/SalutInteroperability - */ - - /* Needed by iChat */ - set_ret = TXTRecordSetValue(&dns_data, "txtvers", 1, "1"); - /* Needed by Gaim/Pidgin <= 2.0.1 (remove at some point) */ - if (set_ret == kDNSServiceErr_NoError) - set_ret = TXTRecordSetValue(&dns_data, "1st", strlen(data->first), data->first); - /* Needed by Gaim/Pidgin <= 2.0.1 (remove at some point) */ - if (set_ret == kDNSServiceErr_NoError) - set_ret = TXTRecordSetValue(&dns_data, "last", strlen(data->last), data->last); - /* Needed by Adium */ - if (set_ret == kDNSServiceErr_NoError) - set_ret = TXTRecordSetValue(&dns_data, "port.p2pj", strlen(portstring), portstring); - /* Needed by iChat, Gaim/Pidgin <= 2.0.1 */ - if (set_ret == kDNSServiceErr_NoError) - set_ret = TXTRecordSetValue(&dns_data, "status", strlen(data->status), data->status); - if (set_ret == kDNSServiceErr_NoError) - set_ret = TXTRecordSetValue(&dns_data, "ver", strlen(VERSION), VERSION); - /* Currently always set to "!" since we don't support AV and wont ever be in a conference */ - if (set_ret == kDNSServiceErr_NoError) - set_ret = TXTRecordSetValue(&dns_data, "vc", strlen(data->vc), data->vc); - if (set_ret == kDNSServiceErr_NoError && email != NULL && *email != '\0') - set_ret = TXTRecordSetValue(&dns_data, "email", strlen(email), email); - if (set_ret == kDNSServiceErr_NoError && jid != NULL && *jid != '\0') - set_ret = TXTRecordSetValue(&dns_data, "jid", strlen(jid), jid); - /* Nonstandard, but used by iChat */ - if (set_ret == kDNSServiceErr_NoError && aim != NULL && *aim != '\0') - set_ret = TXTRecordSetValue(&dns_data, "AIM", strlen(aim), aim); - if (set_ret == kDNSServiceErr_NoError && data->msg != NULL && *data->msg != '\0') - set_ret = TXTRecordSetValue(&dns_data, "msg", strlen(data->msg), data->msg); - if (set_ret == kDNSServiceErr_NoError && data->phsh != NULL && *data->phsh != '\0') - set_ret = TXTRecordSetValue(&dns_data, "phsh", strlen(data->phsh), data->phsh); - - /* TODO: ext, nick, node */ + while (records) { + PurpleKeyValuePair *kvp = records->data; + set_ret = TXTRecordSetValue(&dns_data, kvp->key, strlen(kvp->value), kvp->value); + if (set_ret != kDNSServiceErr_NoError) + break; + records = records->next; + } if (set_ret != kDNSServiceErr_NoError) { purple_debug_error("bonjour", "Unable to allocate memory for text record.\n"); @@ -301,14 +271,15 @@ switch (type) { case PUBLISH_START: - purple_debug_info("bonjour", "Registering service on port %d\n", data->port_p2pj); - err = DNSServiceRegister(&idata->advertisement, 0, 0, purple_account_get_username(data->account), ICHAT_SERVICE, + purple_debug_info("bonjour", "Registering presence on port %d\n", data->port_p2pj); + err = DNSServiceRegister(&idata->presence_svc, 0, 0, purple_account_get_username(data->account), ICHAT_SERVICE, NULL, NULL, htons(data->port_p2pj), TXTRecordGetLength(&dns_data), TXTRecordGetBytesPtr(&dns_data), _mdns_service_register_callback, NULL); break; case PUBLISH_UPDATE: - err = DNSServiceUpdateRecord(idata->advertisement, NULL, 0, TXTRecordGetLength(&dns_data), TXTRecordGetBytesPtr(&dns_data), 0); + purple_debug_info("bonjour", "Updating presence.\n"); + err = DNSServiceUpdateRecord(idata->presence_svc, NULL, 0, TXTRecordGetLength(&dns_data), TXTRecordGetBytesPtr(&dns_data), 0); break; } @@ -316,9 +287,12 @@ purple_debug_error("bonjour", "Failed to publish presence service.\n"); ret = FALSE; } else if (type == PUBLISH_START) { - /* hack: Bonjour on windows is broken. We don't care about the callback but we have to listen anyway */ - gint fd = DNSServiceRefSockFD(idata->advertisement); - idata->advertisement_handler = purple_input_add(fd, PURPLE_INPUT_READ, _mdns_handle_event, idata->advertisement); + /* We need to do this because according to the Apple docs: + * "the client is responsible for ensuring that DNSServiceProcessResult() is called + * whenever there is a reply from the daemon - the daemon may terminate its connection + * with a client that does not process the daemon's responses */ + idata->presence_handler = purple_input_add(DNSServiceRefSockFD(idata->presence_svc), + PURPLE_INPUT_READ, _mdns_handle_event, idata->presence_svc); } } @@ -332,37 +306,64 @@ g_return_val_if_fail(idata != NULL, FALSE); - return (DNSServiceBrowse(&idata->browser, 0, 0, ICHAT_SERVICE, NULL, + if (DNSServiceBrowse(&idata->browser_svc, 0, 0, ICHAT_SERVICE, NULL, _mdns_service_browse_callback, data->account) - == kDNSServiceErr_NoError); -} + == kDNSServiceErr_NoError) { + idata->browser_handler = purple_input_add(DNSServiceRefSockFD(idata->browser_svc), + PURPLE_INPUT_READ, _mdns_handle_event, idata->browser_svc); + return TRUE; + } -guint _mdns_register_to_mainloop(BonjourDnsSd *data) { - Win32SessionImplData *idata = data->mdns_impl_data; - - g_return_val_if_fail(idata != NULL, 0); - - return purple_input_add(DNSServiceRefSockFD(idata->browser), - PURPLE_INPUT_READ, _mdns_handle_event, idata->browser); + return FALSE; } void _mdns_stop(BonjourDnsSd *data) { Win32SessionImplData *idata = data->mdns_impl_data; - if (idata == NULL || idata->advertisement == NULL || idata->browser == NULL) + if (idata == NULL) return; - /* hack: for win32, we need to stop listening to the advertisement pipe too */ - purple_input_remove(idata->advertisement_handler); + if (idata->presence_svc != NULL) { + purple_input_remove(idata->presence_handler); + DNSServiceRefDeallocate(idata->presence_svc); + } - DNSServiceRefDeallocate(idata->advertisement); - DNSServiceRefDeallocate(idata->browser); + if (idata->browser_svc != NULL) { + purple_input_remove(idata->browser_handler); + DNSServiceRefDeallocate(idata->browser_svc); + } g_free(idata); data->mdns_impl_data = NULL; } +gboolean _mdns_set_buddy_icon_data(BonjourDnsSd *data, gconstpointer avatar_data, gsize avatar_len) { + Win32SessionImplData *idata = data->mdns_impl_data; + DNSServiceErrorType err = kDNSServiceErr_NoError; + + g_return_val_if_fail(idata != NULL, FALSE); + + if (avatar_data != NULL && idata->buddy_icon_rec == NULL) { + purple_debug_info("bonjour", "Setting new buddy icon.\n"); + err = DNSServiceAddRecord(idata->presence_svc, &idata->buddy_icon_rec, + 0, kDNSServiceType_NULL, avatar_len, avatar_data, 0); + } else if (avatar_data != NULL) { + purple_debug_info("bonjour", "Updating existing buddy icon.\n"); + err = DNSServiceUpdateRecord(idata->presence_svc, idata->buddy_icon_rec, + 0, avatar_len, avatar_data, 0); + } else if (idata->buddy_icon_rec != NULL) { + purple_debug_info("bonjour", "Removing existing buddy icon.\n"); + DNSServiceRemoveRecord(idata->presence_svc, idata->buddy_icon_rec, 0); + idata->buddy_icon_rec = NULL; + } + + if (err != kDNSServiceErr_NoError) + purple_debug_error("bonjour", "Error (%d) setting buddy icon record.\n", err); + + return (err == kDNSServiceErr_NoError); +} + void _mdns_init_buddy(BonjourBuddy *buddy) { buddy->mdns_impl_data = g_new0(Win32BuddyImplData, 1); } @@ -387,8 +388,9 @@ buddy->mdns_impl_data = NULL; } -void bonjour_dns_sd_retrieve_buddy_icon(BonjourBuddy* buddy) { +void _mdns_retrieve_buddy_icon(BonjourBuddy* buddy) { Win32BuddyImplData *idata = buddy->mdns_impl_data; + char svc_name[kDNSServiceMaxDomainName]; g_return_if_fail(idata != NULL); @@ -400,10 +402,11 @@ idata->null_query = NULL; } - if (kDNSServiceErr_NoError == DNSServiceQueryRecord(&idata->null_query, 0, 0, buddy->full_service_name, - kDNSServiceType_NULL, kDNSServiceClass_IN, _mdns_text_record_query_callback, buddy)) { - int fd = DNSServiceRefSockFD(idata->null_query); - idata->null_query_handler = purple_input_add(fd, PURPLE_INPUT_READ, _mdns_handle_event, idata->null_query); + DNSServiceConstructFullName(svc_name, buddy->name, ICHAT_SERVICE, "local"); + if (kDNSServiceErr_NoError == DNSServiceQueryRecord(&idata->null_query, 0, kDNSServiceInterfaceIndexAny, svc_name, + kDNSServiceType_NULL, kDNSServiceClass_IN, _mdns_record_query_callback, buddy)) { + idata->null_query_handler = purple_input_add(DNSServiceRefSockFD(idata->null_query), + PURPLE_INPUT_READ, _mdns_handle_event, idata->null_query); } }
--- a/libpurple/protocols/jabber/auth.c Sat Aug 11 21:08:06 2007 +0000 +++ b/libpurple/protocols/jabber/auth.c Sat Aug 11 21:08:27 2007 +0000 @@ -296,7 +296,7 @@ purple_request_yes_no(js->gc, _("Plaintext Authentication"), _("Plaintext Authentication"), msg, - 2, js->gc->account, NULL, NULL, NULL, + 2, js->gc->account, NULL, NULL, js->gc->account, allow_cyrus_plaintext_auth, disallow_plaintext_auth); g_free(msg);
--- a/libpurple/protocols/jabber/jabber.c Sat Aug 11 21:08:06 2007 +0000 +++ b/libpurple/protocols/jabber/jabber.c Sat Aug 11 21:08:27 2007 +0000 @@ -1718,7 +1718,7 @@ static PurpleCmdRet jabber_cmd_chat_role(PurpleConversation *conv, const char *cmd, char **args, char **error, void *data) { - JabberChat *chat; + JabberChat *chat = jabber_chat_find_by_conv(conv); if (!chat || !args || !args[0] || !args[1]) return PURPLE_CMD_RET_FAILED; @@ -1731,8 +1731,6 @@ return PURPLE_CMD_RET_FAILED; } - chat = jabber_chat_find_by_conv(conv); - if (!jabber_chat_role_user(chat, args[0], args[1])) { *error = g_strdup_printf(_("Unable to set role \"%s\" for user: %s"), args[1], args[0]);
--- a/pidgin/Makefile.am Sat Aug 11 21:08:06 2007 +0000 +++ b/pidgin/Makefile.am Sat Aug 11 21:08:27 2007 +0000 @@ -65,7 +65,7 @@ pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = pidgin.pc -SUBDIRS = pixmaps plugins sounds +SUBDIRS = pixmaps plugins bin_PROGRAMS = pidgin
--- a/pidgin/gtkaccount.c Sat Aug 11 21:08:06 2007 +0000 +++ b/pidgin/gtkaccount.c Sat Aug 11 21:08:27 2007 +0000 @@ -2546,9 +2546,15 @@ } static void * -pidgin_accounts_request_authorization(PurpleAccount *account, const char *remote_user, - const char *id, const char *alias, const char *message, gboolean on_list, - GCallback auth_cb, GCallback deny_cb, void *user_data) +pidgin_accounts_request_authorization(PurpleAccount *account, + const char *remote_user, + const char *id, + const char *alias, + const char *message, + gboolean on_list, + PurpleAccountRequestAuthorizationCb auth_cb, + PurpleAccountRequestAuthorizationCb deny_cb, + void *user_data) { char *buffer; PurpleConnection *gc; @@ -2574,8 +2580,8 @@ if (!on_list) { struct auth_and_add *aa = g_new0(struct auth_and_add, 1); - aa->auth_cb = (PurpleAccountRequestAuthorizationCb)auth_cb; - aa->deny_cb = (PurpleAccountRequestAuthorizationCb)deny_cb; + aa->auth_cb = auth_cb; + aa->deny_cb = deny_cb; aa->data = user_data; aa->username = g_strdup(remote_user); aa->alias = g_strdup(alias);
--- a/pidgin/gtkconv.c Sat Aug 11 21:08:06 2007 +0000 +++ b/pidgin/gtkconv.c Sat Aug 11 21:08:27 2007 +0000 @@ -8337,11 +8337,6 @@ if (gdk_window_get_state(w->window) & GDK_WINDOW_STATE_MAXIMIZED) return FALSE; - /* don't save if nothing changed */ - if (x == purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/x") && - y == purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/y")) - return FALSE; /* carry on normally */ - /* don't save off-screen positioning */ if (x + event->width < 0 || y + event->height < 0 || @@ -8389,10 +8384,10 @@ static void pidgin_conv_restore_position(PidginWindow *win) { pidgin_conv_set_position_size(win, - purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/x"), - purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/y"), - purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/width"), - purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/height")); + purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/x"), + purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/y"), + purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/width"), + purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/height")); } PidginWindow * @@ -8545,6 +8540,20 @@ gtkconv->tab_cont)); } +static gboolean +close_button_left_cb(GtkWidget *widget, GdkEventCrossing *event, GtkLabel *label) +{ + gtk_label_set_markup(label, "×"); + return FALSE; +} + +static gboolean +close_button_entered_cb(GtkWidget *widget, GdkEventCrossing *event, GtkLabel *label) +{ + gtk_label_set_markup(label, "<b>×</b>"); + return FALSE; +} + void pidgin_conv_window_add_gtkconv(PidginWindow *win, PidginConversation *gtkconv) { @@ -8567,15 +8576,17 @@ /* Close button. */ gtkconv->close = gtk_event_box_new(); gtk_event_box_set_visible_window(GTK_EVENT_BOX(gtkconv->close), FALSE); - close_image = gtk_label_new(NULL); - gtk_label_set_markup(GTK_LABEL(close_image),"<b>×</b>"); + gtk_widget_set_events(gtkconv->close, GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK); + close_image = gtk_label_new("×"); + g_signal_connect(G_OBJECT(gtkconv->close), "enter-notify-event", G_CALLBACK(close_button_entered_cb), close_image); + g_signal_connect(G_OBJECT(gtkconv->close), "leave-notify-event", G_CALLBACK(close_button_left_cb), close_image); gtk_widget_show(close_image); gtk_container_add(GTK_CONTAINER(gtkconv->close), close_image); gtk_tooltips_set_tip(gtkconv->tooltips, gtkconv->close, _("Close conversation"), NULL); g_signal_connect(G_OBJECT(gtkconv->close), "button-press-event", - G_CALLBACK(close_conv_cb), gtkconv); + G_CALLBACK(close_conv_cb), gtkconv); #if !GTK_CHECK_VERSION(2,6,0) /* @@ -8596,7 +8607,7 @@ gtkconv->tab_label = gtk_label_new(tmp_lab = purple_conversation_get_title(conv)); gtkconv->menu_tabby = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); - gtkconv->menu_label = gtk_label_new(purple_conversation_get_title(gtkconv->active_conv)); + gtkconv->menu_label = gtk_label_new(tmp_lab); gtk_box_pack_start(GTK_BOX(gtkconv->menu_tabby), gtkconv->menu_icon, FALSE, FALSE, 0); gtk_widget_show_all(gtkconv->menu_icon); @@ -8606,7 +8617,6 @@ gtk_misc_set_alignment(GTK_MISC(gtkconv->menu_label), 0, 0); gtk_widget_show(gtkconv->menu_tabby); - gtk_widget_set_size_request(gtkconv->menu_tabby, 0, -1); if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) pidgin_conv_update_buddy_icon(conv); @@ -8621,9 +8631,6 @@ if (pidgin_conv_window_get_gtkconv_count(win) == 1) { /* Er, bug in notebooks? Switch to the page manually. */ gtk_notebook_set_current_page(GTK_NOTEBOOK(win->notebook), 0); - - gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win->notebook), - purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/tabs")); } else { gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win->notebook), TRUE); } @@ -8722,6 +8729,12 @@ !tabs_side && !angle && pidgin_conv_window_get_gtkconv_count(win) > 1, TRUE, GTK_PACK_START); + if (pidgin_conv_window_get_gtkconv_count(win) == 1) + gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win->notebook), + !purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/im/show_buddy_icons") || + purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/tab_side") == GTK_POS_LEFT || + purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/tab_side") == GTK_POS_RIGHT); + /* show the widgets */ /* gtk_widget_show(gtkconv->icon); */ gtk_widget_show(gtkconv->tab_label); @@ -8745,12 +8758,6 @@ gtk_notebook_remove_page(GTK_NOTEBOOK(win->notebook), index); - /* go back to tabless */ - if (pidgin_conv_window_get_gtkconv_count(win) <= 2) { - gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win->notebook), - purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/tabs")); - } - win->gtkconvs = g_list_remove(win->gtkconvs, gtkconv); if (win->gtkconvs && win->gtkconvs->next == NULL) @@ -9029,6 +9036,7 @@ PidginWindow *win; win = pidgin_conv_window_new(); + g_signal_connect(G_OBJECT(win->window), "configure_event", G_CALLBACK(gtk_conv_configure_cb), NULL);
--- a/pidgin/gtkdocklet.c Sat Aug 11 21:08:06 2007 +0000 +++ b/pidgin/gtkdocklet.c Sat Aug 11 21:08:27 2007 +0000 @@ -121,7 +121,7 @@ /* determine if any ims have unseen messages */ convs = get_pending_list(DOCKLET_TOOLTIP_LINE_LIMIT); - if (!strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/docklet/show"), "always")) { + if (!strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/docklet/show"), "pending")) { if (convs && ui_ops->create && !visible) { g_list_free(convs); ui_ops->create(); @@ -636,7 +636,7 @@ purple_prefs_add_none(PIDGIN_PREFS_ROOT "/docklet"); purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/docklet/blink", FALSE); - purple_prefs_add_string(PIDGIN_PREFS_ROOT "/docklet/show", "pending"); + purple_prefs_add_string(PIDGIN_PREFS_ROOT "/docklet/show", "always"); purple_prefs_connect_callback(docklet_handle, PIDGIN_PREFS_ROOT "/docklet/show", docklet_show_pref_changed_cb, NULL);
--- a/pidgin/gtkimhtmltoolbar.c Sat Aug 11 21:08:06 2007 +0000 +++ b/pidgin/gtkimhtmltoolbar.c Sat Aug 11 21:08:27 2007 +0000 @@ -939,7 +939,7 @@ *y -= widget->allocation.height; } -static void pidgin_menu_clicked(GtkWidget *button, GdkEventButton *event, GtkMenu *menu) +static void pidgin_menu_clicked(GtkWidget *button, GtkMenu *menu) { gtk_widget_show_all(GTK_WIDGET(menu)); gtk_menu_popup(menu, NULL, NULL, menu_position_func, button, 0, gtk_get_current_event_time()); @@ -1188,7 +1188,8 @@ gtk_container_foreach(GTK_CONTAINER(menuitem), (GtkCallback)enable_markup, NULL); } - g_signal_connect(G_OBJECT(font_button), "button-press-event", G_CALLBACK(pidgin_menu_clicked), font_menu); + g_signal_connect_swapped(G_OBJECT(font_button), "button-press-event", G_CALLBACK(gtk_widget_activate), font_button); + g_signal_connect(G_OBJECT(font_button), "activate", G_CALLBACK(pidgin_menu_clicked), font_menu); g_signal_connect(G_OBJECT(font_menu), "deactivate", G_CALLBACK(pidgin_menu_deactivate), font_button); /* Sep */ @@ -1229,7 +1230,8 @@ g_signal_connect(G_OBJECT(toolbar->link), "notify::sensitive", G_CALLBACK(button_sensitiveness_changed), menuitem); - g_signal_connect(G_OBJECT(insert_button), "button-press-event", G_CALLBACK(pidgin_menu_clicked), insert_menu); + g_signal_connect_swapped(G_OBJECT(insert_button), "button-press-event", G_CALLBACK(gtk_widget_activate), insert_button); + g_signal_connect(G_OBJECT(insert_button), "activate", G_CALLBACK(pidgin_menu_clicked), insert_menu); g_signal_connect(G_OBJECT(insert_menu), "deactivate", G_CALLBACK(pidgin_menu_deactivate), insert_button); toolbar->sml = NULL; }
--- a/pidgin/gtknotify.c Sat Aug 11 21:08:06 2007 +0000 +++ b/pidgin/gtknotify.c Sat Aug 11 21:08:27 2007 +0000 @@ -274,6 +274,7 @@ gtk_label_set_markup(GTK_LABEL(label), label_text); gtk_label_set_line_wrap(GTK_LABEL(label), TRUE); + gtk_label_set_selectable(GTK_LABEL(label), TRUE); gtk_misc_set_alignment(GTK_MISC(label), 0, 0); gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); @@ -609,6 +610,7 @@ gtk_label_set_markup(GTK_LABEL(label), label_text); gtk_label_set_line_wrap(GTK_LABEL(label), TRUE); + gtk_label_set_selectable(GTK_LABEL(label), TRUE); gtk_misc_set_alignment(GTK_MISC(label), 0, 0); gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0); gtk_widget_show(label); @@ -626,6 +628,7 @@ button = gtk_button_new_from_stock(GTK_STOCK_CLOSE); gtk_box_pack_start(GTK_BOX(vbox), button, FALSE, FALSE, 0); gtk_widget_show(button); + gtk_widget_grab_focus(button); g_signal_connect_swapped(G_OBJECT(button), "clicked", G_CALLBACK(gtk_widget_destroy), window);
--- a/pidgin/gtksound.c Sat Aug 11 21:08:06 2007 +0000 +++ b/pidgin/gtksound.c Sat Aug 11 21:08:27 2007 +0000 @@ -543,7 +543,8 @@ char *filename = g_strdup(purple_prefs_get_path(file_pref)); if(!filename || !strlen(filename)) { g_free(filename); - filename = g_build_filename(DATADIR, "sounds", "pidgin", sounds[event].def, NULL); + /* 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);
--- a/pidgin/sounds/Makefile.am Sat Aug 11 21:08:06 2007 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,12 +0,0 @@ -soundsdir = $(datadir)/sounds/pidgin -sounds_DATA = \ - alert.wav \ - login.wav \ - logout.wav \ - receive.wav \ - send.wav - -EXTRA_DIST = \ - Makefile.mingw \ - $(sounds_DATA) -
--- a/pidgin/sounds/Makefile.mingw Sat Aug 11 21:08:06 2007 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,20 +0,0 @@ -# -# Makefile.mingw -# -# Description: Makefile for win32 (mingw) version of Pidgin sounds -# - -PIDGIN_TREE_TOP := ../.. -include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak - -datadir := $(PIDGIN_INSTALL_DIR) -include ./Makefile.am - -.PHONY: install - -install: - if test '$(sounds_DATA)'; then \ - mkdir -p $(soundsdir); \ - cp $(sounds_DATA) $(soundsdir); \ - fi; -
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/share/Makefile.am Sat Aug 11 21:08:27 2007 +0000 @@ -0,0 +1,2 @@ + +SUBDIRS = sounds
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/share/sounds/Makefile.am Sat Aug 11 21:08:27 2007 +0000 @@ -0,0 +1,12 @@ +soundsdir = $(datadir)/sounds/purple +sounds_DATA = \ + alert.wav \ + login.wav \ + logout.wav \ + receive.wav \ + send.wav + +EXTRA_DIST = \ + Makefile.mingw \ + $(sounds_DATA) +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/share/sounds/Makefile.mingw Sat Aug 11 21:08:27 2007 +0000 @@ -0,0 +1,20 @@ +# +# Makefile.mingw +# +# Description: Makefile for win32 (mingw) version of Pidgin sounds +# + +PIDGIN_TREE_TOP := ../.. +include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak + +datadir := $(PIDGIN_INSTALL_DIR) +include ./Makefile.am + +.PHONY: install + +install: + if test '$(sounds_DATA)'; then \ + mkdir -p $(soundsdir); \ + cp $(sounds_DATA) $(soundsdir); \ + fi; +