Mercurial > pidgin
changeset 19200:d470d42d2cb1
propagate from branch 'im.pidgin.pidgin' (head e687870673f02e5605b0bf9877fd579c78fe8c01)
to branch 'im.pidgin.soc.2007.certmgr' (head d1d514d18e94b8444eef617bb29ee248c3a0a572)
author | William Ehlhardt <williamehlhardt@gmail.com> |
---|---|
date | Sun, 12 Aug 2007 20:48:34 +0000 |
parents | 81163e153778 (current diff) b39aa44b9769 (diff) |
children | 73d8dd2169c4 |
files | libpurple/Makefile.am libpurple/core.c libpurple/protocols/bonjour/mdns_howl.h libpurple/protocols/bonjour/mdns_win32.h libpurple/protocols/jabber/jabber.c pidgin/Makefile.am pidgin/gtkblist.c pidgin/gtkcellrendererexpander.c pidgin/pixmaps/status/16/message-pending.png 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 | 251 files changed, 6062 insertions(+), 1548 deletions(-) [+] |
line wrap: on
line diff
--- a/COPYRIGHT Sun Aug 12 04:03:45 2007 +0000 +++ b/COPYRIGHT Sun Aug 12 20:48:34 2007 +0000 @@ -144,6 +144,7 @@ Gustavo Giráldez Richard Gobeille Ian Goldberg +Matthew Goldstein Michael Golden Charlie Gordon Ryan C. Gordon @@ -245,6 +246,7 @@ Andrew Molloy Michael Monreal Benjamin Moody +John Moody Tim Mooney Sergio Moretto Christian Muise @@ -276,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 Sun Aug 12 04:03:45 2007 +0000 +++ b/ChangeLog Sun Aug 12 20:48:34 2007 +0000 @@ -1,5 +1,23 @@ Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul +version 2.1.1 (??/??/????): + libpurple: + * Added an account action to open your inbox in the yahoo prpl. + * Added support for Unicode status messages in Yahoo. + * 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 + the toolbar + * 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 Sun Aug 12 04:03:45 2007 +0000 +++ b/ChangeLog.API Sun Aug 12 20:48:34 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: @@ -19,7 +29,6 @@ * purple_core_ensure_single_instance This is for UIs to use to ensure only one copy is running. * purple_dbus_is_owner - * purple_image_data_calculate_filename * purple_timeout_add_seconds Callers should prefer this to purple_timeout_add for timers longer than 1 second away. Be aware of the rounding, though.
--- a/ChangeLog.win32 Sun Aug 12 04:03:45 2007 +0000 +++ b/ChangeLog.win32 Sun Aug 12 20:48:34 2007 +0000 @@ -9,6 +9,9 @@ Apple Bonjour for Windows from: http://www.apple.com/support/downloads/bonjourforwindows.html +version 2.0.1 (5/24/2007): + * No changes + version 2.0.0 (5/3/2007): * URI Handler support added via `pidgin.exe --protocolhandler=` * Running a second instance will popup the Buddy List, if possible.
--- a/Doxyfile.in Sun Aug 12 04:03:45 2007 +0000 +++ b/Doxyfile.in Sun Aug 12 20:48:34 2007 +0000 @@ -300,7 +300,7 @@ # by member name. If set to NO (the default) the members will appear in # declaration order. -SORT_BRIEF_DOCS = YES +SORT_BRIEF_DOCS = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be # sorted by fully-qualified names, including namespaces. If set to
--- a/Makefile.am Sun Aug 12 04:03:45 2007 +0000 +++ b/Makefile.am Sun Aug 12 20:48:34 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/Makefile.mingw Sun Aug 12 04:03:45 2007 +0000 +++ b/Makefile.mingw Sun Aug 12 20:48:34 2007 +0000 @@ -67,6 +67,7 @@ $(MAKE) -C $(PURPLE_TOP) -f $(MINGW_MAKEFILE) install $(MAKE) -C $(PIDGIN_TOP) -f $(MINGW_MAKEFILE) install $(MAKE) -C $(PURPLE_PO_TOP) -f $(MINGW_MAKEFILE) install + $(MAKE) -C share -f $(MINGW_MAKEFILE) install create_release_install_dir: install rm -rf $(PIDGIN_INSTALL_DIR).release
--- a/configure.ac Sun Aug 12 04:03:45 2007 +0000 +++ b/configure.ac Sun Aug 12 20:48:34 2007 +0000 @@ -46,17 +46,17 @@ m4_define([purple_lt_current], [1]) m4_define([purple_major_version], [2]) m4_define([purple_minor_version], [1]) -m4_define([purple_micro_version], [0]) -m4_define([purple_version_suffix], []) +m4_define([purple_micro_version], [1]) +m4_define([purple_version_suffix], [devel]) m4_define([purple_version], [purple_major_version.purple_minor_version.purple_micro_version]) m4_define([purple_display_version], purple_version[]m4_ifdef([purple_version_suffix],[purple_version_suffix])) 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])) @@ -593,6 +593,46 @@ AC_SUBST(MEANWHILE_LIBS) dnl ####################################################################### +dnl # Check for Native Avahi headers (for Bonjour) +dnl ####################################################################### +AC_ARG_WITH(avahi-client-includes, [AC_HELP_STRING([--with-avahi-client-includes=DIR], [compile the Bonjour plugin against the Avahi Client includes in DIR])], [ac_avahi_client_includes="$withval"], [ac_avahi_client_includes="no"]) +AC_ARG_WITH(avahi-client-libs, [AC_HELP_STRING([--with-avahi-client-libs=DIR], [compile the Bonjour plugin against the Avahi Client libs in DIR])], [ac_avahi_client_libs="$withval"], [ac_avahi_client_libs="no"]) +AVAHI_CFLAGS="" +AVAHI_LIBS="" + +dnl Attempt to autodetect Avahi +PKG_CHECK_MODULES(AVAHI, [avahi-client avahi-glib], [ + avahiincludes="yes" + avahilibs="yes" +], [ + AC_MSG_RESULT(no) + avahiincludes="no" + avahilibs="no" +]) + +dnl Override AVAHI_CFLAGS if the user specified an include dir +if test "$ac_avahi_client_includes" != "no"; then + AVAHI_CFLAGS="-I$ac_avahi_client_includes" +fi +CPPFLAGS_save="$CPPFLAGS" +CPPFLAGS="$CPPFLAGS $AVAHI_CFLAGS" +AC_CHECK_HEADER(avahi-client/client.h, [avahiincludes=yes], [avahiincludes=no]) +CPPFLAGS="$CPPFLAGS $AVAHI_CFLAGS $GLIB_CFLAGS" +AC_CHECK_HEADER(avahi-glib/glib-malloc.h, [avahiincludes=yes], [avahiincludes=no]) +CPPFLAGS="$CPPFLAGS_save" + +dnl Override AVAHI_LIBS if the user specified a libs dir +if test "$ac_avahi_client_libs" != "no"; then + AVAHI_LIBS="-L$ac_avahi_client_libs -lavahi-common -lavahi-client -lavahi-glib " +fi +AC_CHECK_LIB(avahi-client, avahi_client_new, [avahilibs=yes], [avahilibs=no], $AVAHI_LIBS) + +AC_SUBST(AVAHI_CFLAGS) +AC_SUBST(AVAHI_LIBS) + +AM_CONDITIONAL(MDNS_AVAHI, test "x$avahiincludes" = "xyes" -a "x$avahilibs" = "xyes") + +dnl ####################################################################### dnl # Check for Howl headers (for Bonjour) dnl ####################################################################### AC_ARG_WITH(howl-includes, [AC_HELP_STRING([--with-howl-includes=DIR], [compile the Bonjour plugin against the Howl includes in DIR])], [ac_howl_includes="$withval"], [ac_howl_includes="no"]) @@ -601,6 +641,7 @@ HOWL_LIBS="" dnl Attempt to autodetect avahi-compat-howl +dnl TODO: (This should be removed when the native avahi stuff is stable) PKG_CHECK_MODULES(HOWL, avahi-compat-howl, [ howlincludes="yes" howllibs="yes" @@ -640,6 +681,9 @@ AC_SUBST(HOWL_CFLAGS) AC_SUBST(HOWL_LIBS) +AM_CONDITIONAL(MDNS_HOWL, test "x$howlincludes" = "xyes" -a "x$howllibs" = "xyes") + + dnl ####################################################################### dnl # Check for SILC client includes and libraries dnl ####################################################################### @@ -819,8 +863,10 @@ if test "x$have_meanwhile" != "xyes" ; then STATIC_PRPLS=`echo $STATIC_PRPLS | $sedpath 's/sametime//'` fi -if test "x$howlincludes" != "xyes" -o "x$howllibs" != "xyes"; then - STATIC_PRPLS=`echo $STATIC_PRPLS | $sedpath 's/bonjour//'` +if test "x$avahiincludes" != "xyes" -o "x$avahilibs" != "xyes"; then + if test "x$howlincludes" != "xyes" -o "x$howllibs" != "xyes"; then + STATIC_PRPLS=`echo $STATIC_PRPLS | $sedpath 's/bonjour//'` + fi fi if test "x$silcincludes" != "xyes" -o "x$silcclient" != "xyes"; then STATIC_PRPLS=`echo $STATIC_PRPLS | $sedpath 's/silc/silc10/'` @@ -873,7 +919,7 @@ *) echo "Invalid static protocol $i!!" ; exit ;; esac done -AM_CONDITIONAL(STATIC_BONJOUR, test "x$static_bonjour" = "xyes" -a "x$howlincludes" = "xyes" -a "x$howllibs" = "xyes") +AM_CONDITIONAL(STATIC_BONJOUR, test "x$static_bonjour" = "xyes") AM_CONDITIONAL(STATIC_GG, test "x$static_gg" = "xyes") AM_CONDITIONAL(STATIC_IRC, test "x$static_irc" = "xyes") AM_CONDITIONAL(STATIC_JABBER, test "x$static_jabber" = "xyes") @@ -898,8 +944,10 @@ if test "x$have_meanwhile" != "xyes"; then DYNAMIC_PRPLS=`echo $DYNAMIC_PRPLS | $sedpath 's/sametime//'` fi -if test "x$howlincludes" != "xyes" -o "x$howllibs" != "xyes"; then - DYNAMIC_PRPLS=`echo $DYNAMIC_PRPLS | $sedpath 's/bonjour//'` +if test "x$avahiincludes" != "xyes" -o "x$avahilibs" != "xyes"; then + if test "x$howlincludes" != "xyes" -o "x$howllibs" != "xyes"; then + DYNAMIC_PRPLS=`echo $DYNAMIC_PRPLS | $sedpath 's/bonjour//'` + fi fi if test "x$silcincludes" != "xyes" -o "x$silcclient" != "xyes"; then DYNAMIC_PRPLS=`echo $DYNAMIC_PRPLS | $sedpath 's/silc/silc10/'` @@ -930,7 +978,7 @@ *) echo "Invalid dynamic protocol $i!!" ; exit ;; esac done -AM_CONDITIONAL(DYNAMIC_BONJOUR, test "x$dynamic_bonjour" = "xyes" -a "x$bonjourincludes" = "xyes" -a "x$bonjourclient" = "xyes") +AM_CONDITIONAL(DYNAMIC_BONJOUR, test "x$dynamic_bonjour" = "xyes" -a [ [ "x$avahiincludes" = "xyes" -a "x$avahilibs " = "xyes" ] -o [ "x$howlincludes" = "xyes" -a "x$howllibs" = "xyes" ] ] ) AM_CONDITIONAL(DYNAMIC_GG, test "x$dynamic_gg" = "xyes") AM_CONDITIONAL(DYNAMIC_IRC, test "x$dynamic_irc" = "xyes") AM_CONDITIONAL(DYNAMIC_JABBER, test "x$dynamic_jabber" = "xyes") @@ -2038,6 +2086,8 @@ AC_DEFINE(DEBUG, 1, [Define if debugging is enabled.]) fi +AM_CONDITIONAL(PURPLE_AVAILABLE, true) + AC_OUTPUT([Makefile Doxyfile doc/Makefile @@ -2116,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 @@ -2149,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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/ui-ops.dox Sun Aug 12 20:48:34 2007 +0000 @@ -0,0 +1,24 @@ +/** @page ui-ops UiOps structures + + When implementing a UI for libpurple, you need to fill in various UiOps + structures: + + - #PurpleAccountUiOps + - #PurpleBlistUiOps + - #PurpleConnectionUiOps + - #PurpleConversationUiOps + - #PurpleCoreUiOps + - #PurpleDebugUiOps + - #PurpleDnsQueryUiOps + - #PurpleEventLoopUiOps + - #PurpleIdleUiOps + - #PurpleNotifyUiOps + - #PurplePrivacyUiOps + - #PurpleRequestUiOps + - #PurpleRoomlistUiOps + - #PurpleSoundUiOps + - #PurpleWhiteboardUiOps + - #PurpleXferUiOps + + */ +// vim: ft=c.doxygen
--- a/finch/Makefile.am Sun Aug 12 04:03:45 2007 +0000 +++ b/finch/Makefile.am Sun Aug 12 20:48:34 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 Sun Aug 12 04:03:45 2007 +0000 +++ b/finch/finch.c Sun Aug 12 20:48:34 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,18 +398,29 @@ 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); g_set_prgname("Finch"); +#if GLIB_CHECK_VERSION(2,2,0) 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 Sun Aug 12 04:03:45 2007 +0000 +++ b/finch/finch.h Sun Aug 12 20:48:34 2007 +0000 @@ -27,3 +27,4 @@ #define FINCH_UI "gnt-purple" +#define FINCH_PREFS_ROOT "/finch"
--- a/finch/gntaccount.c Sun Aug 12 04:03:45 2007 +0000 +++ b/finch/gntaccount.c Sun Aug 12 20:48:34 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 Sun Aug 12 04:03:45 2007 +0000 +++ b/finch/gntconv.h Sun Aug 12 20:48:34 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 Sun Aug 12 04:03:45 2007 +0000 +++ b/finch/gntsound.c Sun Aug 12 20:48:34 2007 +0000 @@ -22,28 +22,1053 @@ * along with this program; if not, write to the Free Software * 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 Sun Aug 12 04:03:45 2007 +0000 +++ b/finch/gntui.c Sun Aug 12 20:48:34 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 Sun Aug 12 04:03:45 2007 +0000 +++ b/finch/libgnt/configure.ac Sun Aug 12 20:48:34 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 Sun Aug 12 04:03:45 2007 +0000 +++ b/finch/libgnt/gnt.h Sun Aug 12 20:48:34 2007 +0000 @@ -157,4 +157,3 @@ void (*callback)(int status, gpointer data), gpointer data); gboolean gnt_is_refugee(void); -
--- a/finch/libgnt/gntbindable.c Sun Aug 12 04:03:45 2007 +0000 +++ b/finch/libgnt/gntbindable.c Sun Aug 12 20:48:34 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 */ @@ -184,7 +353,7 @@ action = g_hash_table_lookup(klass->actions, name); if (!action) { - g_printerr("GntWidget: Invalid action name %s for %s\n", + g_printerr("GntBindable: Invalid action name %s for %s\n", name, g_type_name(G_OBJECT_CLASS_TYPE(klass))); if (list) g_list_free(list); @@ -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 Sun Aug 12 04:03:45 2007 +0000 +++ b/finch/libgnt/gntbindable.h Sun Aug 12 20:48:34 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 Sun Aug 12 04:03:45 2007 +0000 +++ b/finch/libgnt/gntcombobox.h Sun Aug 12 20:48:34 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 Sun Aug 12 04:03:45 2007 +0000 +++ b/finch/libgnt/gntfilesel.c Sun Aug 12 20:48:34 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 Sun Aug 12 04:03:45 2007 +0000 +++ b/finch/libgnt/gntline.c Sun Aug 12 20:48:34 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 Sun Aug 12 04:03:45 2007 +0000 +++ b/finch/libgnt/gntmain.c Sun Aug 12 20:48:34 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; @@ -517,7 +529,8 @@ void gnt_screen_release(GntWidget *widget) { - gnt_wm_window_close(wm, widget); + if (wm) + gnt_wm_window_close(wm, widget); } void gnt_screen_update(GntWidget *widget) @@ -564,7 +577,9 @@ void gnt_quit() { - g_hash_table_destroy(wm->nodes); /* XXX: */ + g_object_unref(G_OBJECT(wm)); + wm = NULL; + update_panels(); doupdate(); gnt_uninit_colors();
--- a/finch/libgnt/gntmenu.c Sun Aug 12 04:03:45 2007 +0000 +++ b/finch/libgnt/gntmenu.c Sun Aug 12 20:48:34 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/gnttextview.c Sun Aug 12 04:03:45 2007 +0000 +++ b/finch/libgnt/gnttextview.c Sun Aug 12 20:48:34 2007 +0000 @@ -68,17 +68,31 @@ int i = 0; GList *lines; int rows, scrcol; + int comp = 0; /* Used for top-aligned text */ gboolean has_scroll = !(view->flags & GNT_TEXT_VIEW_NO_SCROLL); wbkgd(widget->window, COLOR_PAIR(GNT_COLOR_NORMAL)); werase(widget->window); + if ((view->flags & GNT_TEXT_VIEW_TOP_ALIGN) && + g_list_length(view->list) < widget->priv.height) { + GList *now = view->list; + comp = widget->priv.height - g_list_length(view->list); + view->list = g_list_nth_prev(view->list, comp); + if (!view->list) { + view->list = g_list_first(now); + comp = widget->priv.height - g_list_length(view->list); + } else { + comp = 0; + } + } + for (i = 0, lines = view->list; i < widget->priv.height && lines; i++, lines = lines->next) { GList *iter; GntTextLine *line = lines->data; - wmove(widget->window, widget->priv.height - 1 - i, 0); + wmove(widget->window, widget->priv.height - 1 - i - comp, 0); for (iter = line->segments; iter; iter = iter->next) { @@ -398,7 +412,7 @@ static void gnt_text_view_size_changed(GntWidget *widget, int w, int h) { - if (w != widget->priv.width) { + if (w != widget->priv.width && GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_MAPPED)) { gnt_text_view_reflow(GNT_TEXT_VIEW(widget)); } } @@ -422,11 +436,16 @@ gnt_text_view_init(GTypeInstance *instance, gpointer class) { GntWidget *widget = GNT_WIDGET(instance); - - GNT_WIDGET_SET_FLAGS(GNT_WIDGET(instance), GNT_WIDGET_GROW_Y | GNT_WIDGET_GROW_X); + GntTextView *view = GNT_TEXT_VIEW(widget); + GntTextLine *line = g_new0(GntTextLine, 1); + GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_NO_BORDER | GNT_WIDGET_NO_SHADOW | + GNT_WIDGET_GROW_Y | GNT_WIDGET_GROW_X); widget->priv.minw = 5; widget->priv.minh = 2; + view->string = g_string_new(NULL); + view->list = g_list_append(view->list, line); + GNTDEBUG; } @@ -464,13 +483,6 @@ GntWidget *gnt_text_view_new() { GntWidget *widget = g_object_new(GNT_TYPE_TEXT_VIEW, NULL); - GntTextView *view = GNT_TEXT_VIEW(widget); - GntTextLine *line = g_new0(GntTextLine, 1); - - GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_NO_BORDER | GNT_WIDGET_NO_SHADOW); - - view->string = g_string_new(NULL); - view->list = g_list_append(view->list, line); return widget; }
--- a/finch/libgnt/gnttextview.h Sun Aug 12 04:03:45 2007 +0000 +++ b/finch/libgnt/gnttextview.h Sun Aug 12 20:48:34 2007 +0000 @@ -47,9 +47,11 @@ typedef struct _GntTextViewPriv GntTextViewPriv; typedef struct _GntTextViewClass GntTextViewClass; -typedef enum _GntTextViewFlag { +typedef enum +{ GNT_TEXT_VIEW_NO_SCROLL = 1 << 0, GNT_TEXT_VIEW_WRAP_CHAR = 1 << 1, + GNT_TEXT_VIEW_TOP_ALIGN = 1 << 2, } GntTextViewFlag; struct _GntTextView
--- a/finch/libgnt/gnttree.c Sun Aug 12 04:03:45 2007 +0000 +++ b/finch/libgnt/gnttree.c Sun Aug 12 20:48:34 2007 +0000 @@ -450,7 +450,7 @@ if (COLUMN_INVISIBLE(tree, i)) { continue; } - mvwaddstr(widget->window, pos, x + 1, tree->columns[i].title); + mvwaddnstr(widget->window, pos, x + (x != pos), tree->columns[i].title, tree->columns[i].width); NEXT_X; } if (pos) @@ -768,6 +768,7 @@ g_string_free(tree->priv->search, TRUE); tree->priv->search = NULL; tree->priv->search_timeout = 0; + GNT_WIDGET_UNSET_FLAGS(GNT_WIDGET(tree), GNT_WIDGET_DISABLE_ACTIONS); } } @@ -984,7 +985,11 @@ g_param_spec_int("columns", "Columns", "Number of columns in the tree.", 1, G_MAXINT, 1, +#if GLIB_CHECK_VERSION(2,8,0) G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB +#else + G_PARAM_READWRITE|G_PARAM_PRIVATE +#endif ) ); @@ -1049,7 +1054,9 @@ GntTree *tree = GNT_TREE(widget); tree->show_separator = TRUE; tree->priv = g_new0(GntTreePriv, 1); - GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_GROW_X | GNT_WIDGET_GROW_Y | GNT_WIDGET_CAN_TAKE_FOCUS); + GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_GROW_X | GNT_WIDGET_GROW_Y | + GNT_WIDGET_CAN_TAKE_FOCUS | GNT_WIDGET_NO_SHADOW); + gnt_widget_set_take_focus(widget, TRUE); widget->priv.minw = 4; widget->priv.minh = 1; GNTDEBUG; @@ -1090,7 +1097,7 @@ free_tree_col(gpointer data) { GntTreeCol *col = data; - if (col->isbinary) + if (!col->isbinary) g_free(col->text); g_free(col); } @@ -1601,9 +1608,6 @@ "columns", col, NULL); - GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_NO_SHADOW); - gnt_widget_set_take_focus(widget, TRUE); - return widget; }
--- a/finch/libgnt/gntutils.h Sun Aug 12 04:03:45 2007 +0000 +++ b/finch/libgnt/gntutils.h Sun Aug 12 20:48:34 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 Sun Aug 12 04:03:45 2007 +0000 +++ b/finch/libgnt/gntwm.c Sun Aug 12 20:48:34 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) { @@ -684,6 +658,8 @@ {'j', "┘"}, {'a', "▒"}, {'n', "┼"}, + {'w', "┬"}, + {'v', "┴"}, {'\0', NULL} }; @@ -841,6 +817,7 @@ shift_right(GntBindable *bindable, GList *null) { GntWM *wm = GNT_WM(bindable); + if (wm->_list.window) return TRUE; @@ -1136,10 +1113,106 @@ 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) +{ + GList *list = *(GList**)p; + list = g_list_prepend(list, window); + *(GList**)p = list; +} + +static void +gnt_wm_destroy(GObject *obj) +{ + GntWM *wm = GNT_WM(obj); + GList *list = NULL; + g_hash_table_foreach(wm->nodes, accumulate_windows, &list); + g_list_foreach(list, (GFunc)gnt_widget_destroy, NULL); + g_list_free(list); + g_hash_table_destroy(wm->nodes); + wm->nodes = NULL; + + while (wm->workspaces) { + g_object_unref(wm->workspaces->data); + wm->workspaces = g_list_delete_link(wm->workspaces, wm->workspaces); + } +} + static void gnt_wm_class_init(GntWMClass *klass) { int i; + GObjectClass *gclass = G_OBJECT_CLASS(klass); + + gclass->dispose = gnt_wm_destroy; klass->new_window = gnt_wm_new_window_real; klass->decorate_window = NULL; @@ -1291,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)); @@ -1679,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; @@ -1897,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; @@ -1978,3 +2067,4 @@ wm->event_stack = set; } +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/finch/libgnt/pygnt/Makefile.am Sun Aug 12 20:48:34 2007 +0000 @@ -0,0 +1,40 @@ +EXTRA_DIST = gendef.sh + +pg_LTLIBRARIES = gnt.la + +pgdir = $(libdir) + +sources = \ + gnt.def \ + gnt.override \ + gntbox.override \ + gntfilesel.override \ + gnttree.override \ + gntwidget.override + +gnt_la_SOURCES = gnt.c common.c common.h gntmodule.c + +gnt_la_LDFLAGS = -module -avoid-version \ + `pkg-config --libs pygobject-2.0` + +gnt_la_LIBADD = \ + $(GLIB_LIBS) \ + ../libgnt.la + +AM_CPPFLAGS = \ + -I../ \ + $(GLIB_CFLAGS) \ + $(GNT_CFLAGS) \ + -I/usr/include/python2.4 \ + `pkg-config --cflags pygobject-2.0` + +CLEANFILES = gnt.def gnt.c gnt.defe + +gnt.def: $(srcdir)/../*.h + $(srcdir)/gendef.sh + +gnt.c: $(sources) + pygtk-codegen-2.0 --prefix gnt \ + --override gnt.override \ + gnt.def > $@ +
--- a/finch/libgnt/pygnt/Makefile.make Sun Aug 12 04:03:45 2007 +0000 +++ b/finch/libgnt/pygnt/Makefile.make Sun Aug 12 20:48:34 2007 +0000 @@ -10,5 +10,7 @@ --override gnt.override \ gnt.def > $@ +#python codegen/codegen.py --prefix gnt \ + clean: @rm *.so *.o gnt.c
--- a/finch/libgnt/pygnt/dbus-gnt Sun Aug 12 04:03:45 2007 +0000 +++ b/finch/libgnt/pygnt/dbus-gnt Sun Aug 12 20:48:34 2007 +0000 @@ -17,13 +17,14 @@ convwins = {} -def buddysignedon(): +def buddysignedon(buddy): pass def conv_closed(conv): key = get_dict_key(conv) stuff = convwins[key] stuff[0].destroy() + # if a conv window is closed, then reopened, this thing crashes convwins[key] = None def wrote_msg(account, who, msg, conv, flags): @@ -31,10 +32,13 @@ tv = stuff[1] tv.append_text_with_flags("\n", 0) tv.append_text_with_flags(strftime("(%X) "), 8) - tv.append_text_with_flags(who + ": ", 1) - tv.append_text_with_flags(msg, 0) + if flags & 3: + tv.append_text_with_flags(who + ": ", 1) + tv.append_text_with_flags(msg, 0) + stuff[0].set_urgent() + else: + tv.append_text_with_flags(msg, 8) tv.scroll(0) - stuff[0].set_urgent() bus = dbus.SessionBus() obj = bus.get_object("im.pidgin.purple.PurpleService", "/im/pidgin/purple/PurpleObject") @@ -72,6 +76,9 @@ purple.PurpleConvChatSend(chatdata, entry.get_text()) entry.clear() +def conv_window_destroyed(win, key): + del convwins[key] + def show_conversation(conv): key = get_dict_key(conv) if key in convwins: @@ -91,27 +98,28 @@ tv.clear() win.show() convwins[key] = [win, tv, entry] + win.connect("destroy", conv_window_destroyed, key) return convwins[key] def show_buddylist(): - win = gnt.Window() - tree = gnt.Tree() - tree.set_property("columns", 1) - win.add_widget(tree) - node = purple.PurpleBlistGetRoot() - while node: - if purple.PurpleBlistNodeIsGroup(node): - sys.stderr.write(str(node) + "\n") - tree.add_row_after(str(node), ["asd", ""], None, None) - #tree.add_row_after(node, [str(purple.PurpleGroupGetName(node)), ""], None, None) - #tree.add_row_after(node, ["aasd", ""], None, None) - elif purple.PurpleBlistNodeIsContact(node): - buddy = purple.PurpleContactGetPriorityBuddy(node) - group = purple.PurpleBuddyGetGroup(buddy) - #tree.add_row_after(node, [str(purple.PurpleBuddyGetName(buddy)), ""], group, None) + win = gnt.Window() + tree = gnt.Tree() + tree.set_property("columns", 1) + win.add_widget(tree) + node = purple.PurpleBlistGetRoot() + while node: + if purple.PurpleBlistNodeIsGroup(node): + sys.stderr.write(str(node) + "\n") + tree.add_row_after(str(node), ["asd", ""], None, None) + #tree.add_row_after(node, [str(purple.PurpleGroupGetName(node)), ""], None, None) + #tree.add_row_after(node, ["aasd", ""], None, None) + elif purple.PurpleBlistNodeIsContact(node): + buddy = purple.PurpleContactGetPriorityBuddy(node) + group = purple.PurpleBuddyGetGroup(buddy) + #tree.add_row_after(node, [str(purple.PurpleBuddyGetName(buddy)), ""], group, None) - node = purple.PurpleBlistNodeNext(node, False) - win.show() + node = purple.PurpleBlistNodeNext(node, False) + win.show() gnt.gnt_init()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/finch/libgnt/pygnt/example/rss/gnthtml.py Sun Aug 12 20:48:34 2007 +0000 @@ -0,0 +1,87 @@ +#!/usr/bin/env python + +""" +gr - An RSS-reader built using libgnt and feedparser. + +Copyright (C) 2007 Sadrul Habib Chowdhury <sadrul@pidgin.im> + +This application is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This application is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this application; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +USA +""" + +""" +This file defines GParser, which is a simple HTML parser to display HTML +in a GntTextView nicely. +""" + +import sgmllib +import gnt + +class GParser(sgmllib.SGMLParser): + def __init__(self, view): + sgmllib.SGMLParser.__init__(self, False) + self.link = None + self.view = view + self.flag = gnt.TEXT_FLAG_NORMAL + + def parse(self, s): + self.feed(s) + self.close() + + def unknown_starttag(self, tag, attrs): + if tag in ["b", "i", "blockquote", "strong"]: + self.flag = self.flag | gnt.TEXT_FLAG_BOLD + elif tag in ["p", "hr", "br"]: + self.view.append_text_with_flags("\n", self.flag) + else: + print tag + + def unknown_endtag(self, tag): + if tag in ["b", "i", "blockquote", "strong"]: + self.flag = self.flag & ~gnt.TEXT_FLAG_BOLD + elif tag in ["p", "hr", "br"]: + self.view.append_text_with_flags("\n", self.flag) + else: + print tag + + def start_u(self, attrs): + self.flag = self.flag | gnt.TEXT_FLAG_UNDERLINE + + def end_u(self): + self.flag = self.flag & ~gnt.TEXT_FLAG_UNDERLINE + + def start_a(self, attributes): + for name, value in attributes: + if name == "href": + self.link = value + + def do_img(self, attrs): + for name, value in attrs: + if name == 'src': + self.view.append_text_with_flags("[img:" + value + "]", self.flag) + + def end_a(self): + if not self.link: + return + self.view.append_text_with_flags(" (", self.flag) + self.view.append_text_with_flags(self.link, self.flag | gnt.TEXT_FLAG_UNDERLINE) + self.view.append_text_with_flags(")", self.flag) + self.link = None + + def handle_data(self, data): + if len(data.strip()) == 0: + return + self.view.append_text_with_flags(data, self.flag) +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/finch/libgnt/pygnt/example/rss/gntrss-ui.py Sun Aug 12 20:48:34 2007 +0000 @@ -0,0 +1,400 @@ +#!/usr/bin/env python + +""" +gr - An RSS-reader built using libgnt and feedparser. + +Copyright (C) 2007 Sadrul Habib Chowdhury <sadrul@pidgin.im> + +This application is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This application is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this application; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +USA +""" + +""" +This file deals with the UI part (gnt) of the application + +TODO: + - Allow showing feeds of only selected 'category' and/or 'priority'. A different + window should be used to change such filtering. + - Display details of each item in its own window. + - Add search capability, and allow searching only in title/body. Also allow + filtering in the search results. + - Show the data and time for feed items (probably in a separate column .. perhaps not) + - Have a simple way to add a feed. + - Allow renaming a feed. +""" + +import gntrss +import gnthtml +import gnt +import gobject +import sys + +__version__ = "0.0.1alpha" +__author__ = "Sadrul Habib Chowdhury (sadrul@pidgin.im)" +__copyright__ = "Copyright 2007, Sadrul Habib Chowdhury" +__license__ = "GPL" # see full license statement above + +gnt.gnt_init() + +class RssTree(gnt.Tree): + __gsignals__ = { + 'active_changed' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_OBJECT,)) + } + + __gntbindings__ = { + 'jump-next-unread' : ('jump_next_unread', 'J') + } + + def jump_next_unread(self, null): + first = None + next = None + all = self.get_rows() + for item in all: + if item.unread: + if next: + first = item + break + elif not first and self.active != item: + first = item + if self.active == item: + next = item + if first: + self.set_active(first) + self.set_selected(first) + return True + + def __init__(self): + self.active = None + gnt.Tree.__init__(self) + gnt.set_flag(self, 8) # remove borders + self.connect('key_pressed', self.do_key_pressed) + + def set_active(self, active): + if self.active == active: + return + if self.active: + flag = gnt.TEXT_FLAG_NORMAL + if self.active.unread: + flag = flag | gnt.TEXT_FLAG_BOLD + self.set_row_flags(self.active, flag) + old = self.active + self.active = active + flag = gnt.TEXT_FLAG_UNDERLINE + if self.active.unread: + flag = flag | gnt.TEXT_FLAG_BOLD + self.set_row_flags(self.active, flag) + self.emit('active_changed', old) + + def do_key_pressed(self, null, text): + if text == '\r': + now = self.get_selection_data() + self.set_active(now) + return True + return False + +gobject.type_register(RssTree) +gnt.register_bindings(RssTree) + +win = gnt.Box(homo = False, vert = True) +win.set_toplevel(True) +win.set_title("GntRss") +win.set_pad(0) + +# +# [[[ Generic feed/item callbacks +# +def feed_item_added(feed, item): + add_feed_item(item) + +def add_feed(feed): + if not feed.get_data('gntrss-connected'): + feed.connect('added', feed_item_added) + feed.connect('notify', update_feed_title) + feed.set_data('gntrss-connected', True) + feeds.add_row_after(feed, [feed.title, str(feed.unread)], None, None) + +def remove_item(item, feed): + items.remove(item) + +def update_feed_item(item, property): + if property.name == 'unread': + if feeds.active == item.parent: + flag = 0 + if item == items.active: + flag = gnt.TEXT_FLAG_UNDERLINE + if item.unread: + flag = flag | gnt.TEXT_FLAG_BOLD + else: + flag = flag | gnt.TEXT_FLAG_NORMAL + items.set_row_flags(item, flag) + + unread = item.parent.unread + if item.unread: + unread = unread + 1 + else: + unread = unread - 1 + item.parent.set_property('unread', unread) + +def add_feed_item(item): + currentfeed = feeds.active + if item.parent != currentfeed: + return + months = ["", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] + dt = str(item.date_parsed[2]) + "." + months[item.date_parsed[1]] + "." + str(item.date_parsed[0]) + items.add_row_after(item, [str(item.title), dt], None, None) + if item.unread: + items.set_row_flags(item, gnt.TEXT_FLAG_BOLD) + if not item.get_data('gntrss-connected'): + item.set_data('gntrss-connected', True) + # this needs to happen *without* having to add the item in the tree + item.connect('notify', update_feed_item) + item.connect('delete', remove_item) + +# +# ]]] Generic feed/item callbacks +# + + +#### +# [[[ The list of feeds +### + +# 'Add Feed' dialog +add_feed_win = None +def add_feed_win_closed(win): + global add_feed_win + add_feed_win = None + +def add_new_feed(): + global add_feed_win + + if add_feed_win: + gnt.gnt_window_present(add_feed_win) + return + win = gnt.Window() + win.set_title("New Feed") + + box = gnt.Box(False, False) + label = gnt.Label("Link") + box.add_widget(label) + entry = gnt.Entry("") + entry.set_size(40, 1) + box.add_widget(entry) + + win.add_widget(box) + win.show() + add_feed_win = win + add_feed_win.connect("destroy", add_feed_win_closed) + +# +# The active row in the feed-list has changed. Update the feed-item table. +def feed_active_changed(tree, old): + items.remove_all() + if not tree.active: + return + update_items_title() + for item in tree.active.items: + add_feed_item(item) + win.give_focus_to_child(items) + +# +# Check for the action keys and decide how to deal with them. +def feed_key_pressed(tree, text): + if tree.is_searching(): + return + if text == 'r': + feed = tree.get_selection_data() + tree.perform_action_key('j') + #tree.perform_action('move-down') + feed.refresh() + elif text == 'R': + feeds = tree.get_rows() + for feed in feeds: + feed.refresh() + elif text == 'm': + feed = tree.get_selection_data() + if feed: + feed.mark_read() + feed.set_property('unread', 0) + elif text == 'a': + add_new_feed() + else: + return False + return True + +feeds = RssTree() +feeds.set_property('columns', 2) +feeds.set_col_width(0, 20) +feeds.set_col_width(1, 6) +feeds.set_column_resizable(0, False) +feeds.set_column_resizable(1, False) +feeds.set_column_is_right_aligned(1, True) +feeds.set_show_separator(False) +feeds.set_column_title(0, "Feeds") +feeds.set_show_title(True) + +feeds.connect('active_changed', feed_active_changed) +feeds.connect('key_pressed', feed_key_pressed) +gnt.unset_flag(feeds, 256) # Fix the width + +#### +# ]]] The list of feeds +### + +#### +# [[[ The list of items in the feed +#### + +# +# The active item in the feed-item list has changed. Update the +# summary content. +def item_active_changed(tree, old): + details.clear() + if not tree.active: + return + item = tree.active + details.append_text_with_flags(str(item.title) + "\n", gnt.TEXT_FLAG_BOLD) + details.append_text_with_flags("Link: ", gnt.TEXT_FLAG_BOLD) + details.append_text_with_flags(str(item.link) + "\n", gnt.TEXT_FLAG_UNDERLINE) + details.append_text_with_flags("Date: ", gnt.TEXT_FLAG_BOLD) + details.append_text_with_flags(str(item.date) + "\n", gnt.TEXT_FLAG_NORMAL) + details.append_text_with_flags("\n", gnt.TEXT_FLAG_NORMAL) + parser = gnthtml.GParser(details) + parser.parse(str(item.summary)) + item.mark_unread(False) + + if old and old.unread: # If the last selected item is marked 'unread', then make sure it's bold + items.set_row_flags(old, gnt.TEXT_FLAG_BOLD) + +# +# Look for action keys in the feed-item list. +def item_key_pressed(tree, text): + if tree.is_searching(): + return + current = tree.get_selection_data() + if text == 'M': # Mark all of the items 'read' + feed = feeds.active + if feed: + feed.mark_read() + elif text == 'm': # Mark the current item 'read' + current.mark_unread(False) + tree.perform_action_key('j') + elif text == 'U': # Mark the current item 'unread' + current.mark_unread(True) + elif text == 'd': + current.remove() + tree.perform_action_key('j') + else: + return False + return True + +items = RssTree() +items.set_property('columns', 2) +items.set_col_width(0, 40) +items.set_col_width(1, 11) +items.set_column_resizable(1, False) +items.set_column_title(0, "Items") +items.set_column_title(1, "Date") +items.set_show_title(True) +items.connect('key_pressed', item_key_pressed) +items.connect('active_changed', item_active_changed) + +#### +# ]]] The list of items in the feed +#### + +# +# Update the title of the items list depending on the selection in the feed list +def update_items_title(): + feed = feeds.active + if feed: + items.set_column_title(0, str(feed.title) + ": " + str(feed.unread) + "(" + str(len(feed.items)) + ")") + else: + items.set_column_title(0, "Items") + items.draw() + +# The container on the top +line = gnt.Line(vertical = False) + +# The textview to show the details of a feed +details = gnt.TextView() +details.set_take_focus(True) +details.set_flag(gnt.TEXT_VIEW_TOP_ALIGN) +details.attach_scroll_widget(details) + +# Make it look nice +s = feeds.get_size() +size = gnt.screen_size() +size[0] = size[0] - s[0] +items.set_size(size[0], size[1] / 2) +details.set_size(size[0], size[1] / 2) + +# Category tree +cat = gnt.Tree() +cat.set_property('columns', 1) +cat.set_column_title(0, 'Category') +cat.set_show_title(True) +gnt.set_flag(cat, 8) # remove borders + +box = gnt.Box(homo = False, vert = False) +box.set_pad(0) + +vbox = gnt.Box(homo = False, vert = True) +vbox.set_pad(0) +vbox.add_widget(feeds) +vbox.add_widget(gnt.Line(False)) +vbox.add_widget(cat) +box.add_widget(vbox) + +box.add_widget(gnt.Line(True)) + +vbox = gnt.Box(homo = False, vert = True) +vbox.set_pad(0) +vbox.add_widget(items) +vbox.add_widget(gnt.Line(False)) +vbox.add_widget(details) +box.add_widget(vbox) + +win.add_widget(box) +win.show() + +def update_feed_title(feed, property): + if property.name == 'title': + if feed.customtitle: + title = feed.customtitle + else: + title = feed.title + feeds.change_text(feed, 0, title) + elif property.name == 'unread': + feeds.change_text(feed, 1, str(feed.unread) + "(" + str(len(feed.items)) + ")") + flag = 0 + if feeds.active == feed: + flag = gnt.TEXT_FLAG_UNDERLINE + update_items_title() + if feed.unread > 0: + flag = flag | gnt.TEXT_FLAG_BOLD + feeds.set_row_flags(feed, flag) + +# populate everything +for feed in gntrss.feeds: + feed.refresh() + feed.set_auto_refresh(True) + add_feed(feed) + +gnt.gnt_register_action("Stuff", add_new_feed) +gnt.gnt_main() + +gnt.gnt_quit() +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/finch/libgnt/pygnt/example/rss/gntrss.py Sun Aug 12 20:48:34 2007 +0000 @@ -0,0 +1,250 @@ +#!/usr/bin/env python + +""" +gr - An RSS-reader built using libgnt and feedparser. + +Copyright (C) 2007 Sadrul Habib Chowdhury <sadrul@pidgin.im> + +This application is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This application is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this application; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +USA +""" + +""" +This file deals with the rss parsing part (feedparser) of the application +""" + +import os +import tempfile, urllib2 +import feedparser +import gobject +import sys +import time + +## +# The FeedItem class. It will update emit 'delete' signal when it's +# destroyed. +## +class FeedItem(gobject.GObject): + __gproperties__ = { + 'unread' : (gobject.TYPE_BOOLEAN, 'read', + 'The unread state of the item.', + False, gobject.PARAM_READWRITE) + } + __gsignals__ = { + 'delete' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_OBJECT,)) + } + def __init__(self, item, parent): + self.__gobject_init__() + try: + "Apparently some feed items don't have any dates in them" + self.date = item['date'] + self.date_parsed = item['date_parsed'] + except: + item['date'] = self.date = time.ctime() + self.date_parsed = feedparser._parse_date(self.date) + + self.title = item['title'].encode('utf8') + self.summary = item['summary'].encode('utf8') + self.link = item['link'] + self.parent = parent + self.unread = True + + def remove(self): + self.emit('delete', self.parent) + if self.unread: + self.parent.set_property('unread', self.parent.unread - 1) + + def do_set_property(self, property, value): + if property.name == 'unread': + self.unread = value + + def mark_unread(self, unread): + if self.unread == unread: + return + self.set_property('unread', unread) + +gobject.type_register(FeedItem) + +def item_hash(item): + return str(item['title']) + +""" +The Feed class. It will update the 'link', 'title', 'desc' and 'items' +attributes if/when they are updated (triggering 'notify::<attr>' signal) + +TODO: + - Add a 'count' attribute + - Each feed will have a 'uidata', which will be its display window + - Look into 'category'. Is it something that feed defines, or the user? + - Have separate refresh times for each feed. + - Have 'priority' for each feed. (somewhat like category, perhaps?) +""" +class Feed(gobject.GObject): + __gproperties__ = { + 'link' : (gobject.TYPE_STRING, 'link', + 'The web page this feed is associated with.', + '...', gobject.PARAM_READWRITE), + 'title' : (gobject.TYPE_STRING, 'title', + 'The title of the feed.', + '...', gobject.PARAM_READWRITE), + 'desc' : (gobject.TYPE_STRING, 'description', + 'The description for the feed.', + '...', gobject.PARAM_READWRITE), + 'items' : (gobject.TYPE_POINTER, 'items', + 'The items in the feed.', gobject.PARAM_READWRITE), + 'unread' : (gobject.TYPE_INT, 'unread', + 'Number of unread items in the feed.', 0, 10000, 0, gobject.PARAM_READWRITE) + } + __gsignals__ = { + 'added' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_OBJECT,)) + } + + def __init__(self, feed): + self.__gobject_init__() + url = feed['link'] + name = feed['name'] + self.url = url # The url of the feed itself + self.link = url # The web page associated with the feed + self.desc = url + self.title = (name, url)[not name] + self.customtitle = name + self.unread = 0 + self.items = [] + self.hash = {} + self.pending = False + self._refresh = {'time' : 30, 'id' : 0} + + def do_set_property(self, property, value): + if property.name == 'link': + self.link = value + elif property.name == 'desc': + self.desc = value + elif property.name == 'title': + self.title = value + elif property.name == 'unread': + self.unread = value + pass + + def set_result(self, result): + # XXX Look at result['bozo'] first, and emit some signal that the UI can use + # to indicate (dim the row?) that the feed has invalid XML format or something + + try: + channel = result['channel'] + self.set_property('link', channel['link']) + self.set_property('desc', channel['description']) + self.set_property('title', channel['title']) + items = result['items'] + except: + items = () + + tmp = {} + for item in self.items: + tmp[hash(item)] = item + + unread = self.unread + for item in items: + try: + exist = self.hash[item_hash(item)] + del tmp[hash(exist)] + except: + itm = FeedItem(item, self) + self.items.append(itm) + self.emit('added', itm) + self.hash[item_hash(item)] = itm + unread = unread + 1 + + if unread != self.unread: + 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" + + self.pending = False + return False + + def refresh(self): + if self.pending: + return + self.pending = True + FeedReader(self).run() + return True + + def mark_read(self): + for item in self.items: + item.mark_unread(False) + + def set_auto_refresh(self, auto): + if auto: + if self._refresh['id']: + return + if self._refresh['time'] < 1: + self._refresh['time'] = 1 + self.id = gobject.timeout_add(self._refresh['time'] * 1000 * 60, self.refresh) + else: + if not self._refresh['id']: + return + gobject.source_remove(self._refresh['id']) + self._refresh['id'] = 0 + +gobject.type_register(Feed) + +""" +The FeedReader updates a Feed. It fork()s off a child to avoid blocking. +""" +class FeedReader: + def __init__(self, feed): + self.feed = feed + + def reap_child(self, pid, status): + result = feedparser.parse(self.tmpfile.name) + self.tmpfile.close() + self.feed.set_result(result) + + def run(self): + self.tmpfile = tempfile.NamedTemporaryFile() + self.pid = os.fork() + if self.pid == 0: + tmp = urllib2.urlopen(self.feed.url) + content = tmp.read() + tmp.close() + self.tmpfile.write(content) + self.tmpfile.flush() + # Do NOT close tmpfile here + os._exit(os.EX_OK) + gobject.child_watch_add(self.pid, self.reap_child) + +feeds = [] +urls = ( + {'name': '/.', + 'link': "http://rss.slashdot.org/Slashdot/slashdot"}, + {'name': 'KernelTrap', + 'link': "http://kerneltrap.org/node/feed"}, + {'name': None, + 'link': "http://pidgin.im/rss.php"}, + {'name': "F1", + 'link': "http://www.formula1.com/rss/news/latest.rss"}, + {'name': "Freshmeat", + 'link': "http://www.pheedo.com/f/freshmeatnet_announcements_unix"}, + {'name': "Cricinfo", + 'link': "http://www.cricinfo.com/rss/livescores.xml"} +) + +for url in urls: + feed = Feed(url) + feeds.append(feed) +
--- a/finch/libgnt/pygnt/gendef.sh Sun Aug 12 04:03:45 2007 +0000 +++ b/finch/libgnt/pygnt/gendef.sh Sun Aug 12 20:48:34 2007 +0000 @@ -28,7 +28,7 @@ gnt.h" # Generate the def file -rm gnt.def +rm -f gnt.def for file in $FILES do python /usr/share/pygtk/2.0/codegen/h2def.py ../$file >> gnt.def @@ -43,6 +43,7 @@ GNT_TYPE_KEY_PRESS_MODE GNT_TYPE_ENTRY_FLAG GNT_TYPE_TEXT_FORMAT_FLAGS +GNT_TYPE_TEXT_VIEW_FLAG " for enum in $ENUMS
--- a/finch/libgnt/pygnt/gnt.override Sun Aug 12 04:03:45 2007 +0000 +++ b/finch/libgnt/pygnt/gnt.override Sun Aug 12 20:48:34 2007 +0000 @@ -29,8 +29,10 @@ #include "common.h" %% include + gntbox.override gntfilesel.override gnttree.override + gntwidget.override %% modulename gnt %% @@ -39,3 +41,154 @@ ignore-glob *_get_gtype %% +define set_flag +static PyObject * +_wrap_set_flag(PyGObject *self, PyObject *args, PyObject *kwargs) +{ + static char *kwlist[] = {"flags", NULL}; + PyGObject *widget; + int flags; + + if (!PyArg_ParseTuple(args, "O!i:gnt.set_flag", &PyGntWidget_Type, &widget, + &flags)) { + return NULL; + } + + GNT_WIDGET_SET_FLAGS(widget->obj, flags); + + Py_INCREF(Py_None); + return Py_None; +} +%% +define unset_flag +static PyObject * +_wrap_unset_flag(PyGObject *self, PyObject *args, PyObject *kwargs) +{ + static char *kwlist[] = {"flags", NULL}; + PyGObject *widget; + int flags; + + if (!PyArg_ParseTuple(args, "O!i:gnt.unset_flag", &PyGntWidget_Type, &widget, + &flags)) { + return NULL; + } + + GNT_WIDGET_UNSET_FLAGS(widget->obj, flags); + + Py_INCREF(Py_None); + return Py_None; +} +%% +define screen_size noargs +static PyObject * +_wrap_screen_size(PyObject *self) +{ + PyObject *list = PyList_New(0); + + if (list == NULL) + return NULL; + + PyList_Append(list, PyInt_FromLong((long)getmaxx(stdscr))); + PyList_Append(list, PyInt_FromLong((long)getmaxy(stdscr))); + + return list; +} +%% +override gnt_register_action +static GHashTable *actions; + + + +static PyObject * +_wrap_gnt_register_action(PyGObject *self, PyObject *args, PyObject *kwargs) +{ + static char *kwlist[] = {"name", "callback", NULL}; + PyGObject *callback; + GClosure *closure; + char *name; + + if (!PyArg_ParseTuple(args, "sO:gnt.gnt_register_action", &name, &callback)) { + return NULL; + } + + if (!PyCallable_Check(callback)) { + PyErr_SetString(PyExc_TypeError, "the callback must be callable ... doh!"); + return NULL; + } + + gnt_register_action(name, callback->obj); + + Py_INCREF(Py_None); + return Py_None; +} +%% +define register_bindings + +static gboolean +pygnt_binding_callback(GntBindable *bindable, GList *list) +{ + PyObject *wrapper = pygobject_new(G_OBJECT(bindable)); + PyObject_CallMethod(wrapper, list->data, "O", Py_None); + Py_DECREF(wrapper); + return TRUE; +} + +static PyObject * +_wrap_register_bindings(PyObject *self, PyObject *args) +{ + PyTypeObject *class; + int pos = 0; + PyObject *key, *value, *gbindings; + GntBindableClass *bindable; + + if (!PyArg_ParseTuple(args, "O!:gnt.register_bindings", + &PyType_Type, &class)) { + /* Make sure it's a GntBindableClass subclass */ + PyErr_SetString(PyExc_TypeError, + "argument must be a GntBindable subclass"); + return NULL; + } + + gbindings = PyDict_GetItemString(class->tp_dict, "__gntbindings__"); + if (!gbindings) + goto end; + + if (!PyDict_Check(gbindings)) { + PyErr_SetString(PyExc_TypeError, + "__gntbindings__ attribute not a dict!"); + return NULL; + } + + bindable = g_type_class_ref(pyg_type_from_object((PyObject *)class)); + while (PyDict_Next(gbindings, &pos, &key, &value)) { + const char *trigger, *callback, *name; + GList *list = NULL; + + if (!PyString_Check(key)) { + PyErr_SetString(PyExc_TypeError, + "__gntbindings__ keys must be strings"); + g_type_class_unref(bindable); + return NULL; + } + name = PyString_AsString(key); + + if (!PyTuple_Check(value) || + !PyArg_ParseTuple(value, "ss", &callback, &trigger)) { + PyErr_SetString(PyExc_TypeError, + "__gntbindings__ values must be (callback, trigger) tupples"); + g_type_class_unref(bindable); + return NULL; + } + + gnt_bindable_class_register_action(bindable, name, pygnt_binding_callback, + trigger, g_strdup(callback), NULL); + } + if (gbindings) + PyDict_DelItemString(class->tp_dict, "__gntbindings__"); + g_type_class_unref(bindable); + +end: + Py_INCREF(Py_None); + return Py_None; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/finch/libgnt/pygnt/gntbox.override Sun Aug 12 20:48:34 2007 +0000 @@ -0,0 +1,38 @@ +/** + * pygnt- Python bindings for the GNT toolkit. + * Copyright (C) 2007 Sadrul Habib Chowdhury <sadrul@pidgin.im> + * + * gntbox.override: overrides for the box widget. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ +%% +override gnt_box_add_widget kwargs +static PyObject * +_wrap_gnt_box_add_widget(PyGObject *self, PyObject *args, PyObject *kwargs) +{ + static char *kwlist[] = { "widget", NULL }; + PyGObject *widget; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!:GntBox.add_widget", kwlist, &PyGntWidget_Type, &widget)) + return NULL; + + gnt_box_add_widget(GNT_BOX(self->obj), GNT_WIDGET(widget->obj)); + Py_INCREF(widget); + + Py_INCREF(Py_None); + return Py_None; +}
--- a/finch/libgnt/pygnt/gntmodule.c Sun Aug 12 04:03:45 2007 +0000 +++ b/finch/libgnt/pygnt/gntmodule.c Sun Aug 12 20:48:34 2007 +0000 @@ -9,11 +9,12 @@ PyObject *m, *d; init_pygobject (); - + m = Py_InitModule ("gnt", gnt_functions); d = PyModule_GetDict (m); gnt_register_classes (d); + gnt_add_constants(m, "GNT_"); if (PyErr_Occurred ()) { Py_FatalError ("can't initialise module sad");
--- a/finch/libgnt/pygnt/gnttree.override Sun Aug 12 04:03:45 2007 +0000 +++ b/finch/libgnt/pygnt/gnttree.override Sun Aug 12 20:48:34 2007 +0000 @@ -20,7 +20,7 @@ * USA */ %% -headrs +headers #include "common.h" %% ignore @@ -49,7 +49,7 @@ return NULL; } while (list) { - PyObject *obj = pyg_pointer_new(G_TYPE_POINTER, list->data); + PyObject *obj = list->data; PyList_Append(py_list, obj); Py_DECREF(obj); list = list->next; @@ -100,4 +100,79 @@ Py_INCREF(Py_None); return Py_None; } +%% +override gnt_tree_get_selection_data noargs +static PyObject * +_wrap_gnt_tree_get_selection_data(PyGObject *self) +{ + PyObject *ret = gnt_tree_get_selection_data(GNT_TREE(self->obj)); + if (!ret) + ret = Py_None; + Py_INCREF(ret); + return ret; +} +%% +override gnt_tree_change_text +static PyObject * +_wrap_gnt_tree_change_text(PyGObject *self, PyObject *args, PyObject *kwargs) +{ + static char *kwlist[] = { "key", "colno", "text", NULL }; + char *text; + int colno; + gpointer key; + if (!PyArg_ParseTupleAndKeywords(args, kwargs,"Ois:GntTree.change_text", kwlist, &key, &colno, &text)) + return NULL; + + gnt_tree_change_text(GNT_TREE(self->obj), key, colno, text); + + Py_INCREF(Py_None); + return Py_None; +} +%% +override gnt_tree_set_row_flags +static PyObject * +_wrap_gnt_tree_set_row_flags(PyGObject *self, PyObject *args, PyObject *kwargs) +{ + static char *kwlist[] = { "key", "flag", NULL }; + int flag; + gpointer key; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs,"Oi:GntTree.set_row_flags", kwlist, &key, &flag)) + return NULL; + + gnt_tree_set_row_flags(GNT_TREE(self->obj), key, flag); + + Py_INCREF(Py_None); + return Py_None; +} +%% +override gnt_tree_remove +static PyObject * +_wrap_gnt_tree_remove(PyGObject *self, PyObject *args, PyObject *kwargs) +{ + static char *kwlist[] = { "key", NULL }; + gpointer key; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs,"O:GntTree.remove", kwlist, &key)) + return NULL; + + gnt_tree_remove(GNT_TREE(self->obj), key); + + Py_INCREF(Py_None); + return Py_None; +} +%% +override gnt_tree_set_selected +static PyObject * +_wrap_gnt_tree_set_selected(PyGObject *self, PyObject *args) +{ + gpointer key; + if (!PyArg_ParseTuple(args, "O:GntTree.set_selected", &key)) { + return NULL; + } + gnt_tree_set_selected(GNT_TREE(self->obj), key); + Py_INCREF(Py_None); + return Py_None; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/finch/libgnt/pygnt/gntwidget.override Sun Aug 12 20:48:34 2007 +0000 @@ -0,0 +1,36 @@ +/** + * pygnt- Python bindings for the GNT toolkit. + * Copyright (C) 2007 Sadrul Habib Chowdhury <sadrul@pidgin.im> + * + * gntwidget.override: overrides for generic widgets. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ +%% +override gnt_widget_get_size args +static PyObject * +_wrap_gnt_widget_get_size(PyGObject *self) +{ + PyObject *list = PyList_New(0); + int x = 0, y = 0; + + gnt_widget_get_size(GNT_WIDGET(self->obj), &x, &y); + PyList_Append(list, PyInt_FromLong((long)x)); + PyList_Append(list, PyInt_FromLong((long)y)); + + return list; +} +
--- a/finch/libgnt/pygnt/test.py Sun Aug 12 04:03:45 2007 +0000 +++ b/finch/libgnt/pygnt/test.py Sun Aug 12 20:48:34 2007 +0000 @@ -1,18 +1,59 @@ #!/usr/bin/python +import gobject import gnt +class MyObject(gobject.GObject): + __gproperties__ = { + 'mytype': (gobject.TYPE_INT, 'mytype', 'the type of the object', + 0, 10000, 0, gobject.PARAM_READWRITE), + 'string': (gobject.TYPE_STRING, 'string property', 'the string', + None, gobject.PARAM_READWRITE), + 'gobject': (gobject.TYPE_OBJECT, 'object property', 'the object', + gobject.PARAM_READWRITE), + } + + def __init__(self, type = 'string', value = None): + self.__gobject_init__() + self.set_property(type, value) + + def do_set_property(self, pspec, value): + if pspec.name == 'string': + self.string = value + self.type = gobject.TYPE_STRING + elif pspec.name == 'gobject': + self.gobject = value + self.type = gobject.TYPE_OBJECT + else: + raise AttributeError, 'unknown property %s' % pspec.name + def do_get_property(self, pspec): + if pspec.name == 'string': + return self.string + elif pspec.name == 'gobject': + return self.gobject + elif pspec.name == 'mytype': + return self.type + else: + raise AttributeError, 'unknown property %s' % pspec.name +gobject.type_register(MyObject) + def button_activate(button, tree): - list = tree.get_selection_text_list() - str = "" - for i in list: - str = str + i - entry.set_text("clicked!!!" + str) + list = tree.get_selection_text_list() + ent = tree.get_selection_data() + if ent.type == gobject.TYPE_STRING: + str = "" + for i in list: + str = str + i + entry.set_text("clicked!!!" + str) + elif ent.type == gobject.TYPE_OBJECT: + ent.gobject.set_text("mwhahaha!!!") gnt.gnt_init() win = gnt.Window() entry = gnt.Entry("") +obj = MyObject() +obj.set_property('gobject', entry) win.add_widget(entry) win.set_title("Entry") @@ -27,12 +68,20 @@ # so random non-string values can be used as the key for a row in a GntTree! last = None for i in range(1, 100): - tree.add_row_after(i, [str(i), ""], None, i-1) -tree.add_row_after(entry, ["asd"], None, None) -tree.add_row_after("b", ["123", ""], entry, None) + key = MyObject('string', str(i)) + tree.add_row_after(key, [str(i)], None, last) + last = key + +tree.add_row_after(MyObject('gobject', entry), ["asd"], None, None) +tree.add_row_after(MyObject('string', "b"), ["123"], MyObject('gobject', entry), None) button.connect("activate", button_activate, tree) +tv = gnt.TextView() + +win.add_widget(tv) +tv.append_text_with_flags("What up!!", gnt.TEXT_FLAG_BOLD) + win.show() gnt.gnt_main()
--- a/finch/libgnt/wms/Makefile.am Sun Aug 12 04:03:45 2007 +0000 +++ b/finch/libgnt/wms/Makefile.am Sun Aug 12 20:48:34 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 Sun Aug 12 04:03:45 2007 +0000 +++ b/finch/libgnt/wms/s.c Sun Aug 12 20:48:34 2007 +0000 @@ -15,6 +15,10 @@ #define TYPE_S (s_get_gtype()) +#ifdef _S +#undef _S +#endif + typedef struct _S { GntWM inherit;
--- a/libpurple/Makefile.am Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/Makefile.am Sun Aug 12 20:48:34 2007 +0000 @@ -150,7 +150,7 @@ # purple dbus server dbus_sources = dbus-server.c dbus-useful.c -dbus_headers = dbus-bindings.h dbus-purple.h dbus-server.h dbus-useful.h dbus-define-api.h +dbus_headers = dbus-bindings.h dbus-purple.h dbus-server.h dbus-useful.h dbus-define-api.h dbus-types.h dbus_exported = dbus-useful.h dbus-define-api.h account.h blist.h buddyicon.h \ connection.h conversation.h core.h ft.h log.h notify.h prefs.h roomlist.h \
--- a/libpurple/account.c Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/account.c Sun Aug 12 20:48:34 2007 +0000 @@ -1130,8 +1130,8 @@ void * purple_account_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) + const char *id, const char *alias, const char *message, gboolean on_list, + PurpleAccountRequestAuthorizationCb auth_cb, PurpleAccountRequestAuthorizationCb deny_cb, void *user_data) { PurpleAccountUiOps *ui_ops; PurpleAccountRequestInfo *info; @@ -1146,8 +1146,8 @@ info->type = PURPLE_ACCOUNT_REQUEST_AUTHORIZATION; info->account = account; info->ui_handle = ui_ops->request_authorize(account, remote_user, id, alias, message, - on_list, auth_cb, deny_cb, user_data); - + on_list, auth_cb, deny_cb, user_data); + handles = g_list_append(handles, info); return info->ui_handle; }
--- a/libpurple/account.h Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/account.h Sun Aug 12 20:48:34 2007 +0000 @@ -51,20 +51,50 @@ PURPLE_ACCOUNT_REQUEST_AUTHORIZATION = 0 /* Account authorization request */ } PurpleAccountRequestType; + +/** Account UI operations, used to notify the user of status changes and when + * buddies add this account to their buddy lists. + */ struct _PurpleAccountUiOps { - /* A buddy we already have added us to their buddy list. */ - void (*notify_added)(PurpleAccount *account, const char *remote_user, - const char *id, const char *alias, + /** A buddy who is already on this account's buddy list added this account + * to their buddy list. + */ + void (*notify_added)(PurpleAccount *account, + const char *remote_user, + const char *id, + const char *alias, const char *message); - void (*status_changed)(PurpleAccount *account, PurpleStatus *status); - /* Someone we don't have on our list added us. Will prompt to add them. */ - void (*request_add)(PurpleAccount *account, const char *remote_user, - const char *id, const char *alias, + + /** This account's status changed. */ + void (*status_changed)(PurpleAccount *account, + PurpleStatus *status); + + /** Someone we don't have on our list added us; prompt to add them. */ + void (*request_add)(PurpleAccount *account, + const char *remote_user, + const char *id, + const char *alias, const char *message); - void *(*request_authorize)(PurpleAccount *account, const char *remote_user, const char *id, - const char *alias, const char *message, gboolean on_list, - GCallback authorize_cb, GCallback deny_cb, void *user_data); + + /** Prompt for authorization when someone adds this account to their buddy + * list. To authorize them to see this account's presence, call \a + * authorize_cb (\a user_data); otherwise call \a deny_cb (\a user_data); + * @return a UI-specific handle, as passed to #close_account_request. + */ + void *(*request_authorize)(PurpleAccount *account, + const char *remote_user, + const char *id, + const char *alias, + const char *message, + gboolean on_list, + PurpleAccountRequestAuthorizationCb authorize_cb, + PurpleAccountRequestAuthorizationCb deny_cb, + void *user_data); + + /** Close a pending request for authorization. \a ui_handle is a handle + * as returned by #request_authorize. + */ void (*close_account_request)(void *ui_handle); void (*_purple_reserved1)(void); @@ -193,7 +223,7 @@ /** * Notifies the user that a remote user has wants to add the local user - * to his or her buddy list and requires authorization to d oso. + * to his or her buddy list and requires authorization to do so. * * This will present a dialog informing the user of this and ask if the * user authorizes or denies the remote user from adding him. @@ -212,7 +242,7 @@ */ void *purple_account_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); + PurpleAccountRequestAuthorizationCb auth_cb, PurpleAccountRequestAuthorizationCb deny_cb, void *user_data); /** * Close account requests registered for the given PurpleAccount
--- a/libpurple/connection.h Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/connection.h Sun Aug 12 20:48:34 2007 +0000 @@ -60,15 +60,52 @@ #include "plugin.h" #include "status.h" +/** Connection UI operations. Used to notify the user of changes to + * connections, such as being disconnected, and to respond to the + * underlying network connection appearing and disappearing. UIs should + * call #purple_connections_set_ui_ops() with an instance of this struct. + * + * @see @ref ui-ops + */ typedef struct { - void (*connect_progress)(PurpleConnection *gc, const char *text, - size_t step, size_t step_count); + /** When an account is connecting, this operation is called to notify + * the UI of what is happening, as well as which @a step out of @a + * step_count has been reached (which might be displayed as a progress + * bar). + */ + void (*connect_progress)(PurpleConnection *gc, + const char *text, + size_t step, + size_t step_count); + /** Called when a connection is established (just before the + * @ref signed-on signal). + */ void (*connected)(PurpleConnection *gc); + /** Called when a connection is ended (between the @ref signing-off + * and @ref signed-off signals). + */ void (*disconnected)(PurpleConnection *gc); + /** Used to display connection-specific notices. (Pidgin's Gtk user + * interface implements this as a no-op; #purple_connection_notice(), + * which uses this operation, is not used by any of the protocols + * shipped with libpurple.) + */ void (*notice)(PurpleConnection *gc, const char *text); + /** Called when an error causes a connection to be disconnected. + * Called before #disconnected. + * @param text a localized error message. + */ void (*report_disconnect)(PurpleConnection *gc, const char *text); + /** Called when libpurple discovers that the computer's network + * connection is active. On Linux, this uses Network Manager if + * available; on Windows, it uses Win32's network change notification + * infrastructure. + */ void (*network_connected)(); + /** Called when libpurple discovers that the computer's network + * connection has gone away. + */ void (*network_disconnected)(); void (*_purple_reserved1)(void);
--- a/libpurple/conversation.c Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/conversation.c Sun Aug 12 20:48:34 2007 +0000 @@ -1106,7 +1106,7 @@ c = purple_conv_im_get_conversation(im); - /* Raise the window, if specified in prefs. */ + /* Pass this on to either the ops structure or the default write func. */ if (c->ui_ops != NULL && c->ui_ops->write_im != NULL) c->ui_ops->write_im(c, who, message, flags, mtime); else
--- a/libpurple/conversation.h Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/conversation.h Sun Aug 12 20:48:34 2007 +0000 @@ -149,27 +149,74 @@ */ struct _PurpleConversationUiOps { + /** Called when @a conv is created (but before the @ref + * conversation-created signal is emitted). + */ void (*create_conversation)(PurpleConversation *conv); + + /** Called just before @a conv is freed. */ void (*destroy_conversation)(PurpleConversation *conv); + /** Write a message to a chat. If this field is @c NULL, libpurple will + * fall back to using #write_conv. + * @see purple_conv_chat_write() + */ void (*write_chat)(PurpleConversation *conv, const char *who, const char *message, PurpleMessageFlags flags, time_t mtime); + /** Write a message to an IM conversation. If this field is @c NULL, + * libpurple will fall back to using #write_conv. + * @see purple_conv_im_write() + */ void (*write_im)(PurpleConversation *conv, const char *who, const char *message, PurpleMessageFlags flags, time_t mtime); - void (*write_conv)(PurpleConversation *conv, const char *name, const char *alias, - const char *message, PurpleMessageFlags flags, + /** Write a message to a conversation. This is used rather than + * the chat- or im-specific ops for generic messages, such as system + * messages like "x is now know as y". + * @see purple_conversation_write() + */ + void (*write_conv)(PurpleConversation *conv, + const char *name, + const char *alias, + const char *message, + PurpleMessageFlags flags, time_t mtime); - void (*chat_add_users)(PurpleConversation *conv, GList *cbuddies, gboolean new_arrivals); - + /** Add @a cbuddies to a chat. + * @param cbuddies A @C GList of #PurpleConvChatBuddy structs. + * @param new_arrivals Whether join notices should be shown. + * (Join notices are actually written to the + * conversation by #purple_conv_chat_add_users().) + */ + void (*chat_add_users)(PurpleConversation *conv, + GList *cbuddies, + gboolean new_arrivals); + /** Rename the user in this chat named @a old_name to @a new_name. (The + * rename message is written to the conversation by libpurple.) + * @param new_alias @a new_name's new alias, if they have one. + * @see purple_conv_chat_add_users() + */ void (*chat_rename_user)(PurpleConversation *conv, const char *old_name, const char *new_name, const char *new_alias); + /** Remove @a users from a chat. + * @param users A @C GList of <tt>const char *</tt>s. + * @see purple_conv_chat_rename_user() + */ void (*chat_remove_users)(PurpleConversation *conv, GList *users); + /** Called when a user's flags are changed. + * @see purple_conv_chat_user_set_flags() + */ void (*chat_update_user)(PurpleConversation *conv, const char *user); + /** Present this conversation to the user; for example, by displaying + * the IM dialog. + */ void (*present)(PurpleConversation *conv); + /** If this UI has a concept of focus (as in a windowing system) and + * this conversation has the focus, return @c TRUE; otherwise, return + * @c FALSE. + */ gboolean (*has_focus)(PurpleConversation *conv); /* Custom Smileys */ @@ -178,6 +225,11 @@ const guchar *data, gsize size); void (*custom_smiley_close)(PurpleConversation *conv, const char *smile); + /** Prompt the user for confirmation to send @a message. This function + * should arrange for the message to be sent if the user accepts. If + * this field is @c NULL, libpurple will fall back to using + * #purple_request_action(). + */ void (*send_confirm)(PurpleConversation *conv, const char *message); void (*_purple_reserved1)(void);
--- a/libpurple/core.c Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/core.c Sun Aug 12 20:48:34 2007 +0000 @@ -161,8 +161,9 @@ /* * Call this early on to try to auto-detect our IP address and * hopefully save some time later. + * TODO: do this here after purple_prefs_load() has been moved into purple_prefs_init() */ - purple_network_get_my_ip(-1); + /*purple_network_get_my_ip(-1);*/ if (ops != NULL && ops->ui_init != NULL) ops->ui_init();
--- a/libpurple/log.c Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/log.c Sun Aug 12 20:48:34 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/plugins/log_reader.c Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/plugins/log_reader.c Sun Aug 12 20:48:34 2007 +0000 @@ -60,14 +60,14 @@ char *path; GDir *dir; - g_return_val_if_fail(sn != NULL, list); - g_return_val_if_fail(account != NULL, list); + g_return_val_if_fail(sn != NULL, NULL); + g_return_val_if_fail(account != NULL, NULL); logdir = purple_prefs_get_string("/plugins/core/log_reader/adium/log_directory"); /* By clearing the log directory path, this logger can be (effectively) disabled. */ - if (!*logdir) - return list; + if (!logdir || !*logdir) + return NULL; plugin = purple_find_prpl(purple_account_get_protocol_id(account)); if (!plugin) @@ -236,7 +236,8 @@ /* XXX: TODO: We probably want to set PURPLE_LOG_READ_NO_NEWLINE * XXX: TODO: for HTML logs. */ - *flags = 0; + if (flags != NULL) + *flags = 0; g_return_val_if_fail(log != NULL, g_strdup("")); @@ -625,17 +626,17 @@ const char *old_session_id = ""; struct msn_logger_data *data = NULL; - g_return_val_if_fail(sn != NULL, list); - g_return_val_if_fail(account != NULL, list); + g_return_val_if_fail(sn != NULL, NULL); + g_return_val_if_fail(account != NULL, NULL); if (strcmp(account->protocol_id, "prpl-msn")) - return list; + return NULL; logdir = purple_prefs_get_string("/plugins/core/log_reader/msn/log_directory"); /* By clearing the log directory path, this logger can be (effectively) disabled. */ - if (!*logdir) - return list; + if (!logdir || !*logdir) + return NULL; buddy = purple_find_buddy(account, sn); @@ -874,7 +875,8 @@ GString *text = NULL; xmlnode *message; - *flags = PURPLE_LOG_READ_NO_NEWLINE; + if (flags != NULL) + *flags = PURPLE_LOG_READ_NO_NEWLINE; g_return_val_if_fail(log != NULL, g_strdup("")); data = log->logger_data; @@ -1119,7 +1121,7 @@ if (name_guessed != NAME_GUESS_UNKNOWN) text = g_string_append(text, "</span>"); - style = xmlnode_get_attrib(text_node, "Style"); + style = xmlnode_get_attrib(text_node, "Style"); tmp = xmlnode_get_data(text_node); if (style && *style) { @@ -1209,14 +1211,14 @@ gchar *line; gchar *c; - g_return_val_if_fail(sn != NULL, list); - g_return_val_if_fail(account != NULL, list); + g_return_val_if_fail(sn != NULL, NULL); + g_return_val_if_fail(account != NULL, NULL); logdir = purple_prefs_get_string("/plugins/core/log_reader/trillian/log_directory"); /* By clearing the log directory path, this logger can be (effectively) disabled. */ - if (!*logdir) - return list; + if (!logdir || !*logdir) + return NULL; plugin = purple_find_prpl(purple_account_get_protocol_id(account)); if (!plugin) @@ -1427,7 +1429,9 @@ char *c; const char *line; - *flags = PURPLE_LOG_READ_NO_NEWLINE; + if (flags != NULL) + *flags = PURPLE_LOG_READ_NO_NEWLINE; + g_return_val_if_fail(log != NULL, g_strdup("")); data = log->logger_data; @@ -1772,18 +1776,18 @@ int offset = 0; GError *error; - g_return_val_if_fail(sn != NULL, list); - g_return_val_if_fail(account != NULL, list); + g_return_val_if_fail(sn != NULL, NULL); + g_return_val_if_fail(account != NULL, NULL); /* QIP only supports ICQ. */ if (strcmp(account->protocol_id, "prpl-icq")) - return list; + return NULL; logdir = purple_prefs_get_string("/plugins/core/log_reader/qip/log_directory"); /* By clearing the log directory path, this logger can be (effectively) disabled. */ - if (!*logdir) - return list; + if (!logdir || !*logdir) + return NULL; plugin = purple_find_prpl(purple_account_get_protocol_id(account)); if (!plugin) @@ -1923,7 +1927,9 @@ char *utf8_string; FILE *file; - *flags = PURPLE_LOG_READ_NO_NEWLINE; + if (flags != NULL) + *flags = PURPLE_LOG_READ_NO_NEWLINE; + g_return_val_if_fail(log != NULL, g_strdup("")); data = log->logger_data;
--- a/libpurple/plugins/perl/perl-handlers.c Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/plugins/perl/perl-handlers.c Sun Aug 12 20:48:34 2007 +0000 @@ -22,6 +22,7 @@ gchar *hvname; PurplePlugin *plugin; PurplePerlScript *gps; + STRLEN na; dSP; plugin = action->plugin; @@ -45,9 +46,16 @@ XPUSHs(purple_perl_bless_object(gps->plugin, "Purple::Plugin")); PUTBACK; - call_sv(*callback, G_VOID | G_DISCARD); + call_sv(*callback, G_EVAL | G_VOID | G_DISCARD); + SPAGAIN; + if (SvTRUE(ERRSV)) { + purple_debug_error("perl", + "Perl plugin action function exited abnormally: %s\n", + SvPV(ERRSV, na)); + } + PUTBACK; FREETMPS; LEAVE; @@ -59,6 +67,7 @@ GList *l = NULL; PurplePerlScript *gps; int i = 0, count = 0; + STRLEN na; dSP; gps = (PurplePerlScript *)plugin->info->extra_info; @@ -77,10 +86,16 @@ XPUSHs(&PL_sv_undef); PUTBACK; - count = call_pv(gps->plugin_action_sub, G_ARRAY); + count = call_pv(gps->plugin_action_sub, G_EVAL | G_ARRAY); SPAGAIN; + if (SvTRUE(ERRSV)) { + purple_debug_error("perl", + "Perl plugin actions lookup exited abnormally: %s\n", + SvPV(ERRSV, na)); + } + if (count == 0) croak("The plugin_actions sub didn't return anything.\n"); @@ -113,6 +128,7 @@ MAGIC *mg; GtkWidget *ret; PurplePerlScript *gps; + STRLEN na; dSP; gps = (PurplePerlScript *)plugin->info->extra_info; @@ -120,13 +136,19 @@ ENTER; SAVETMPS; - count = call_pv(gps->gtk_prefs_sub, G_SCALAR | G_NOARGS); + count = call_pv(gps->gtk_prefs_sub, G_EVAL | G_SCALAR | G_NOARGS); if (count != 1) croak("call_pv: Did not return the correct number of values.\n"); /* the frame was created in a perl sub and is returned */ SPAGAIN; + if (SvTRUE(ERRSV)) { + purple_debug_error("perl", + "Perl gtk plugin frame init exited abnormally: %s\n", + SvPV(ERRSV, na)); + } + /* We have a Gtk2::Frame on top of the stack */ sv = POPs; @@ -150,6 +172,7 @@ int count; PurplePerlScript *gps; PurplePluginPrefFrame *ret_frame; + STRLEN na; dSP; gps = (PurplePerlScript *)plugin->info->extra_info; @@ -161,10 +184,16 @@ PUSHMARK(SP); PUTBACK; - count = call_pv(gps->prefs_sub, G_SCALAR | G_NOARGS); + count = call_pv(gps->prefs_sub, G_EVAL | G_SCALAR | G_NOARGS); SPAGAIN; + if (SvTRUE(ERRSV)) { + purple_debug_error("perl", + "Perl plugin prefs frame init exited abnormally: %s\n", + SvPV(ERRSV, na)); + } + if (count != 1) croak("call_pv: Did not return the correct number of values.\n"); /* the frame was created in a perl sub and is returned */ @@ -215,6 +244,7 @@ { PurplePerlTimeoutHandler *handler = (PurplePerlTimeoutHandler *)data; gboolean ret = FALSE; + STRLEN na; dSP; ENTER; @@ -225,6 +255,12 @@ call_sv(handler->callback, G_EVAL | G_SCALAR); SPAGAIN; + if (SvTRUE(ERRSV)) { + purple_debug_error("perl", + "Perl timeout function exited abnormally: %s\n", + SvPV(ERRSV, na)); + } + ret = POPi; PUTBACK; @@ -285,7 +321,7 @@ else ret_val = purple_perl_data_from_sv(ret_value, POPs); } else { - call_sv(handler->callback, G_SCALAR); + call_sv(handler->callback, G_EVAL | G_SCALAR); SPAGAIN; } @@ -501,6 +537,7 @@ gchar **args, gchar **error, void *data) { int i = 0, count, ret_value = PURPLE_CMD_RET_OK; + STRLEN na; SV *cmdSV, *tmpSV, *convSV; PurplePerlCmdHandler *handler = (PurplePerlCmdHandler *)data; @@ -532,11 +569,17 @@ } PUTBACK; - count = call_sv(handler->callback, G_EVAL|G_SCALAR); + count = call_sv(handler->callback, G_EVAL | G_SCALAR); if (count != 1) croak("call_sv: Did not return the correct number of values.\n"); + if (SvTRUE(ERRSV)) { + purple_debug_error("perl", + "Perl plugin command function exited abnormally: %s\n", + SvPV(ERRSV, na)); + } + SPAGAIN; ret_value = POPi;
--- a/libpurple/prefs.c Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/prefs.c Sun Aug 12 20:48:34 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/Makefile.am Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/bonjour/Makefile.am Sun Aug 12 20:48:34 2007 +0000 @@ -1,7 +1,7 @@ EXTRA_DIST = \ - mdns_win32.c \ - mdns_win32.h \ - Makefile.mingw + mdns_win32.c \ + dns_sd_proxy.h \ + Makefile.mingw pkgdir = $(libdir)/purple-$(PURPLE_MAJOR_VERSION) @@ -10,16 +10,24 @@ bonjour.h \ buddy.c \ buddy.h \ - dns_sd_proxy.h \ jabber.c \ jabber.h \ mdns_common.c \ mdns_common.h \ - mdns_howl.c \ - mdns_howl.h \ - mdns_types.h + mdns_interface.h \ + mdns_types.h \ + parser.c \ + parser.h -AM_CFLAGS = $(st) -DUSE_BONJOUR_HOWL +if MDNS_AVAHI + BONJOURSOURCES += mdns_avahi.c +else +if MDNS_HOWL + BONJOURSOURCES += mdns_howl.c +endif +endif + +AM_CFLAGS = $(st) libbonjour_la_LDFLAGS = -module -avoid-version @@ -29,14 +37,29 @@ noinst_LIBRARIES = libbonjour.a libbonjour_a_SOURCES = $(BONJOURSOURCES) libbonjour_a_CFLAGS = $(AM_CFLAGS) -libbonjour_a_LIBADD = $(HOWL_LIBS) +libbonjour_a_LIBADD = + +if MDNS_AVAHI + libbonjour_a_LIBADD += $(AVAHI_LIBS) +else +if MDNS_HOWL + libbonjour_a_LIBADD += $(HOWL_LIBS) +endif +endif else st = pkg_LTLIBRARIES = libbonjour.la libbonjour_la_SOURCES = $(BONJOURSOURCES) -libbonjour_la_LIBADD = $(GLIB_LIBS) $(HOWL_LIBS) +libbonjour_la_LIBADD = $(GLIB_LIBS) $(LIBXML_LIBS) +if MDNS_AVAHI + libbonjour_la_LIBADD += $(AVAHI_LIBS) +else +if MDNS_HOWL + libbonjour_la_LIBADD += $(HOWL_LIBS) +endif +endif endif @@ -46,4 +69,13 @@ -I$(top_builddir)/libpurple \ $(GLIB_CFLAGS) \ $(DEBUG_CFLAGS) \ - $(HOWL_CFLAGS) + $(LIBXML_CFLAGS) + +if MDNS_AVAHI + AM_CPPFLAGS += $(AVAHI_CFLAGS) +else +if MDNS_HOWL + AM_CPPFLAGS += $(HOWL_CFLAGS) +endif +endif +
--- a/libpurple/protocols/bonjour/Makefile.mingw Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/bonjour/Makefile.mingw Sun Aug 12 20:48:34 2007 +0000 @@ -30,12 +30,14 @@ -I$(GTK_TOP)/include/glib-2.0 \ -I$(GTK_TOP)/lib/glib-2.0/include \ -I$(BONJOUR_TOP)/include \ + -I$(LIBXML2_TOP)/include \ -I$(PURPLE_TOP) \ -I$(PURPLE_TOP)/win32 \ -I$(PIDGIN_TREE_TOP) LIB_PATHS += -L$(GTK_TOP)/lib \ -L$(BONJOUR_TOP)/lib \ + -L$(LIBXML2_TOP)/lib \ -L$(PURPLE_TOP) ## @@ -45,6 +47,7 @@ buddy.c \ mdns_common.c \ mdns_win32.c \ + parser.c \ jabber.c OBJECTS = $(C_SRC:%.c=%.o) @@ -58,6 +61,7 @@ -lintl \ -ldnssd \ -lnetapi32 \ + -lxml2 \ -lpurple include $(PIDGIN_COMMON_RULES)
--- a/libpurple/protocols/bonjour/bonjour.c Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/bonjour/bonjour.c Sun Aug 12 20:48:34 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 Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/bonjour/buddy.c Sun Aug 12 20:48:34 2007 +0000 @@ -18,10 +18,12 @@ #include <stdlib.h> #include "internal.h" +#include "cipher.h" #include "buddy.h" #include "account.h" #include "blist.h" #include "bonjour.h" +#include "mdns_interface.h" #include "debug.h" /** @@ -35,9 +37,31 @@ buddy->account = account; buddy->name = g_strdup(name); + _mdns_init_buddy(buddy); + 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; @@ -102,11 +126,11 @@ { PurpleBuddy *buddy; PurpleGroup *group; - const char *status_id, *first, *last; - gchar *alias; + PurpleAccount *account = bonjour_buddy->account; + 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; @@ -116,44 +140,95 @@ * field from the DNS SD. */ - /* Create the alias for the buddy using the first and the last name */ - first = bonjour_buddy->first; - last = bonjour_buddy->last; - alias = g_strdup_printf("%s%s%s", - (first && *first ? first : ""), - (first && *first && last && *last ? " " : ""), - (last && *last ? last : "")); - /* Make sure the Bonjour group exists in our buddy list */ group = purple_find_group(BONJOUR_GROUP_NAME); /* Use the buddy's domain, instead? */ - if (group == NULL) - { + if (group == NULL) { group = purple_group_new(BONJOUR_GROUP_NAME); purple_blist_add_group(group, NULL); } /* Make sure the buddy exists in our buddy list */ - buddy = purple_find_buddy(bonjour_buddy->account, bonjour_buddy->name); + buddy = purple_find_buddy(account, bonjour_buddy->name); - if (buddy == NULL) - { - buddy = purple_buddy_new(bonjour_buddy->account, bonjour_buddy->name, alias); + if (buddy == NULL) { + buddy = purple_buddy_new(account, bonjour_buddy->name, NULL); buddy->proto_data = bonjour_buddy; purple_blist_node_set_flags((PurpleBlistNode *)buddy, PURPLE_BLIST_NODE_FLAG_NO_SAVE); purple_blist_add_buddy(buddy, NULL, group, NULL); } + /* Create the alias for the buddy using the first and the last name */ + 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) - purple_prpl_got_user_status(bonjour_buddy->account, buddy->name, status_id, - "message", bonjour_buddy->msg, - NULL); + purple_prpl_got_user_status(account, buddy->name, status_id, + "message", bonjour_buddy->msg, NULL); else - purple_prpl_got_user_status(bonjour_buddy->account, buddy->name, status_id, - NULL); - purple_prpl_got_user_idle(bonjour_buddy->account, buddy->name, FALSE, 0); + purple_prpl_got_user_status(account, buddy->name, status_id, NULL); + + purple_prpl_got_user_idle(account, buddy->name, FALSE, 0); + + /* TODO: Because we don't save Bonjour buddies in blist.xml, + * we will always have to look up the buddy icon at login time. + * I think we should figure out a way to do something about this. */ + + /* Deal with the buddy icon */ + old_hash = purple_buddy_icons_get_checksum_for_user(buddy); + 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 if (!new_hash) + purple_buddy_icons_set_for_user(account, buddy->name, NULL, 0, NULL); +} - g_free(alias); +/** + * 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); } /** @@ -164,7 +239,6 @@ { g_free(buddy->name); g_free(buddy->ip); - g_free(buddy->first); g_free(buddy->phsh); g_free(buddy->status); @@ -182,13 +256,8 @@ bonjour_jabber_close_conversation(buddy->conversation); buddy->conversation = NULL; -#ifdef USE_BONJOUR_APPLE - if (buddy->txt_query != NULL) - { - purple_input_remove(buddy->txt_query_fd); - DNSServiceRefDeallocate(buddy->txt_query); - } -#endif + /* Clean up any mdns implementation data */ + _mdns_delete_buddy(buddy); g_free(buddy); }
--- a/libpurple/protocols/bonjour/buddy.h Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/bonjour/buddy.h Sun Aug 12 20:48:34 2007 +0000 @@ -19,21 +19,15 @@ #include <glib.h> -#include "config.h" #include "account.h" #include "jabber.h" -#ifdef USE_BONJOUR_APPLE -#include "dns_sd_proxy.h" -#else /* USE_BONJOUR_HOWL */ -#include <howl.h> -#endif - typedef struct _BonjourBuddy { PurpleAccount *account; gchar *name; + /* TODO: Remove and just use the hostname */ gchar *ip; gint port_p2pj; @@ -53,11 +47,7 @@ BonjourJabberConversation *conversation; -#ifdef USE_BONJOUR_APPLE - DNSServiceRef txt_query; - int txt_query_fd; -#endif - + gpointer mdns_impl_data; } BonjourBuddy; static const char *const buddy_TXT_records[] = { @@ -85,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. @@ -100,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 Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/bonjour/issues.txt Sun Aug 12 20:48:34 2007 +0000 @@ -3,6 +3,5 @@ ========================================== * Status changes don't work -* Avatars * File transfers * Typing notifications
--- a/libpurple/protocols/bonjour/jabber.c Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/bonjour/jabber.c Sun Aug 12 20:48:34 2007 +0000 @@ -42,6 +42,7 @@ #include "util.h" #include "jabber.h" +#include "parser.h" #include "bonjour.h" #include "buddy.h" @@ -109,9 +110,10 @@ } static void -_jabber_parse_and_write_message_to_ui(xmlnode *message_node, PurpleConnection *connection, PurpleBuddy *pb) +_jabber_parse_and_write_message_to_ui(xmlnode *message_node, PurpleBuddy *pb) { xmlnode *body_node, *html_node, *events_node; + PurpleConnection *gc = pb->account->gc; char *body, *html_body = NULL; const char *ichat_balloon_color = NULL; const char *ichat_text_color = NULL; @@ -186,7 +188,7 @@ /* TODO: Should we do something with "composing_event" here? */ /* Send the message to the UI */ - serv_got_im(connection, pb->name, body, 0, time(NULL)); + serv_got_im(gc, pb->name, body, 0, time(NULL)); g_free(body); g_free(html_body); @@ -218,37 +220,6 @@ } } -static gint -_read_data(gint socket, char **message) -{ - GString *data = g_string_new(""); - char partial_data[512]; - gint total_message_length = 0; - gint partial_message_length = 0; - - /* Read chunks of 512 bytes till the end of the data */ - while ((partial_message_length = recv(socket, partial_data, 512, 0)) > 0) - { - g_string_append_len(data, partial_data, partial_message_length); - total_message_length += partial_message_length; - } - - if (partial_message_length == -1) - { - if (errno != EAGAIN) - purple_debug_warning("bonjour", "receive error: %s\n", strerror(errno)); - if (total_message_length == 0) { - return -1; - } - } - - *message = g_string_free(data, FALSE); - if (total_message_length != 0) - purple_debug_info("bonjour", "Receive: -%s- %d bytes\n", *message, total_message_length); - - return total_message_length; -} - static void _send_data_write_cb(gpointer data, gint source, PurpleInputCondition cond) { @@ -303,7 +274,8 @@ /* If we're not ready to actually send, append it to the buffer */ if (bconv->tx_handler != -1 || bconv->connect_data != NULL - || !bconv->stream_started + || !bconv->sent_stream_start + || !bconv->recv_stream_start || purple_circ_buffer_get_max_read(bconv->tx_buf) > 0) { ret = -1; errno = EAGAIN; @@ -341,20 +313,32 @@ return ret; } +void bonjour_jabber_process_packet(PurpleBuddy *pb, xmlnode *packet) { + if (!strcmp(packet->name, "message")) + _jabber_parse_and_write_message_to_ui(packet, pb); + else + purple_debug_warning("bonjour", "Unknown packet: %s\n", + packet->name); +} + + static void _client_socket_handler(gpointer data, gint socket, PurpleInputCondition condition) { - char *message = NULL; - gint message_length; PurpleBuddy *pb = data; - PurpleAccount *account = pb->account; - BonjourBuddy *bb = pb->proto_data; - gboolean closed_conversation = FALSE; + gint len, message_length; + static char message[4096]; + + /*TODO: use a static buffer */ /* Read the data from the socket */ - if ((message_length = _read_data(socket, &message)) == -1) { + if ((len = recv(socket, message, sizeof(message) - 1, 0)) == -1) { /* There have been an error reading from the socket */ if (errno != EAGAIN) { + BonjourBuddy *bb = pb->proto_data; + + purple_debug_warning("bonjour", "receive error: %s\n", strerror(errno)); + bonjour_jabber_close_conversation(bb->conversation); bb->conversation = NULL; @@ -362,65 +346,71 @@ * If they try to send another message it'll reconnect */ } return; - } else if (message_length == 0) { /* The other end has closed the socket */ - closed_conversation = TRUE; + } else if (len == 0) { /* The other end has closed the socket */ + purple_debug_warning("bonjour", "Connection closed (without stream end) by %s.\n", pb->name); + bonjour_jabber_stream_ended(pb); + return; } else { + message_length = len; message[message_length] = '\0'; - while (g_ascii_iscntrl(message[message_length - 1])) { + while (message_length > 0 && g_ascii_iscntrl(message[message_length - 1])) { message[message_length - 1] = '\0'; message_length--; } } - /* - * Check that this is not the end of the conversation. This is - * using a magic string, but xmlnode won't play nice when just - * parsing an end tag - */ - if (closed_conversation || purple_str_has_prefix(message, STREAM_END)) { - PurpleConversation *conv; + purple_debug_info("bonjour", "Receive: -%s- %d bytes\n", message, len); + + bonjour_parser_process(pb, message, message_length); +} - /* Close the socket, clear the watcher and free memory */ - bonjour_jabber_close_conversation(bb->conversation); - bb->conversation = NULL; +void bonjour_jabber_stream_ended(PurpleBuddy *pb) { + BonjourBuddy *bb = pb->proto_data; + PurpleConversation *conv; + + purple_debug_info("bonjour", "Recieved conversation close notification from %s.\n", pb->name); + + /* Close the socket, clear the watcher and free memory */ + bonjour_jabber_close_conversation(bb->conversation); + bb->conversation = NULL; - /* Inform the user that the conversation has been closed */ - conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, pb->name, account); - if (conv != NULL) { - char *tmp = g_strdup_printf(_("%s has closed the conversation."), pb->name); - purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL)); - g_free(tmp); - } - } else { - xmlnode *message_node; + /* Inform the user that the conversation has been closed */ + conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, pb->name, pb->account); + if (conv != NULL) { + char *tmp = g_strdup_printf(_("%s has closed the conversation."), pb->name); + purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL)); + g_free(tmp); + } +} - /* Parse the message into an XMLnode for analysis */ - message_node = xmlnode_from_str(message, strlen(message)); +void bonjour_jabber_stream_started(PurpleBuddy *pb) { + BonjourBuddy *bb = pb->proto_data; + BonjourJabberConversation *bconv = bb->conversation; - if (message_node != NULL) { - /* Parse the message to get the data and send to the ui */ - _jabber_parse_and_write_message_to_ui(message_node, account->gc, pb); - xmlnode_free(message_node); - } else { - /* TODO: Deal with receiving only a partial message */ - } + /* If the stream has been completely started, we can start doing stuff */ + if (bconv->sent_stream_start && bconv->recv_stream_start && purple_circ_buffer_get_max_read(bconv->tx_buf) > 0) { + /* Watch for when we can write the buffered messages */ + bconv->tx_handler = purple_input_add(bconv->socket, PURPLE_INPUT_WRITE, + _send_data_write_cb, pb); + /* We can probably write the data right now. */ + _send_data_write_cb(pb, bconv->socket, PURPLE_INPUT_WRITE); } - g_free(message); } struct _stream_start_data { char *msg; - PurpleInputFunction tx_handler_cb; }; + static void _start_stream(gpointer data, gint source, PurpleInputCondition condition) { PurpleBuddy *pb = data; BonjourBuddy *bb = pb->proto_data; - struct _stream_start_data *ss = bb->conversation->stream_data; + BonjourJabberConversation *bconv = bb->conversation; + struct _stream_start_data *ss = bconv->stream_data; int len, ret; len = strlen(ss->msg); @@ -443,7 +433,7 @@ _("Unable to send the message, the conversation couldn't be started."), PURPLE_MESSAGE_SYSTEM, time(NULL)); - bonjour_jabber_close_conversation(bb->conversation); + bonjour_jabber_close_conversation(bconv); bb->conversation = NULL; return; @@ -457,22 +447,67 @@ return; } - /* Stream started; process the send buffer if there is one*/ - purple_input_remove(bb->conversation->tx_handler); - bb->conversation->tx_handler= -1; - - bb->conversation->stream_started = TRUE; - g_free(ss->msg); g_free(ss); - bb->conversation->stream_data = NULL; + bconv->stream_data = NULL; + + /* Stream started; process the send buffer if there is one */ + purple_input_remove(bconv->tx_handler); + bconv->tx_handler= -1; + bconv->sent_stream_start = TRUE; + + bonjour_jabber_stream_started(pb); + +} + +static gboolean bonjour_jabber_stream_init(PurpleBuddy *pb, int client_socket) +{ + int ret, len; + char *stream_start; + BonjourBuddy *bb = pb->proto_data; + + stream_start = g_strdup_printf(DOCTYPE, purple_account_get_username(pb->account), + purple_buddy_get_name(pb)); + len = strlen(stream_start); + + /* Start the stream */ + ret = send(client_socket, stream_start, len, 0); + + if (ret == -1 && errno == EAGAIN) + ret = 0; + else if (ret <= 0) { + const char *err = strerror(errno); - if (ss->tx_handler_cb) { - bb->conversation->tx_handler = purple_input_add(source, PURPLE_INPUT_WRITE, - ss->tx_handler_cb, pb); - /* We can probably write the data now. */ - (ss->tx_handler_cb)(pb, source, PURPLE_INPUT_WRITE); + purple_debug_error("bonjour", "Error starting stream with buddy %s at %s:%d error: %s\n", + purple_buddy_get_name(pb), bb->ip ? bb->ip : "(null)", bb->port_p2pj, err ? err : "(null)"); + + close(client_socket); + g_free(stream_start); + + return FALSE; } + + /* This is unlikely to happen */ + if (ret < len) { + struct _stream_start_data *ss = g_new(struct _stream_start_data, 1); + ss->msg = g_strdup(stream_start + ret); + bb->conversation->stream_data = ss; + /* Finish sending the stream start */ + bb->conversation->tx_handler = purple_input_add(client_socket, + PURPLE_INPUT_WRITE, _start_stream, pb); + } else + bb->conversation->sent_stream_start = TRUE; + + g_free(stream_start); + + /* setup the parser fresh for each stream */ + bonjour_parser_setup(bb->conversation); + + bb->conversation->socket = client_socket; + bb->conversation->rx_handler = purple_input_add(client_socket, + PURPLE_INPUT_READ, _client_socket_handler, pb); + + return TRUE; } static void @@ -498,6 +533,7 @@ /* Look for the buddy that has opened the conversation and fill information */ address_text = inet_ntoa(their_addr.sin_addr); + purple_debug_info("bonjour", "Received incoming connection from %s\n.", address_text); cbba = g_new0(struct _check_buddy_by_address_t, 1); cbba->address = address_text; cbba->pb = &pb; @@ -515,49 +551,15 @@ /* Check if the conversation has been previously started */ if (bb->conversation == NULL) { - int ret, len; - char *stream_start = g_strdup_printf(DOCTYPE, purple_account_get_username(pb->account), - purple_buddy_get_name(pb)); - - len = strlen(stream_start); - - /* Start the stream */ - ret = send(client_socket, stream_start, len, 0); + bb->conversation = bonjour_jabber_conv_new(); - if (ret == -1 && errno == EAGAIN) - ret = 0; - else if (ret <= 0) { - const char *err = strerror(errno); - - purple_debug_error("bonjour", "Error starting stream with buddy %s at %s:%d error: %s\n", - purple_buddy_get_name(pb), bb->ip ? bb->ip : "(null)", bb->port_p2pj, err ? err : "(null)"); - + if (!bonjour_jabber_stream_init(pb, client_socket)) { close(client_socket); - g_free(stream_start); - return; } - bb->conversation = bonjour_jabber_conv_new(); - bb->conversation->socket = client_socket; - bb->conversation->rx_handler = purple_input_add(client_socket, - PURPLE_INPUT_READ, _client_socket_handler, pb); - - /* This is unlikely to happen */ - if (ret < len) { - struct _stream_start_data *ss = g_new(struct _stream_start_data, 1); - ss->msg = g_strdup(stream_start + ret); - ss->tx_handler_cb = NULL; /* We have nothing to write yet */ - bb->conversation->stream_data = ss; - /* Finish sending the stream start */ - bb->conversation->tx_handler = purple_input_add(client_socket, - PURPLE_INPUT_WRITE, _start_stream, pb); - } else { - bb->conversation->stream_started = TRUE; - } - - g_free(stream_start); } else { + purple_debug_warning("bonjour", "Ignoring incoming connection because an existing connection exists.\n"); close(client_socket); } } @@ -639,8 +641,6 @@ { PurpleBuddy *pb = data; BonjourBuddy *bb = pb->proto_data; - int len, ret; - char *stream_start; bb->conversation->connect_data = NULL; @@ -661,15 +661,7 @@ return; } - stream_start = g_strdup_printf(DOCTYPE, purple_account_get_username(pb->account), purple_buddy_get_name(pb)); - len = strlen(stream_start); - - /* Start the stream and send queued messages */ - ret = send(source, stream_start, len, 0); - - if (ret == -1 && errno == EAGAIN) - ret = 0; - else if (ret <= 0) { + if (!bonjour_jabber_stream_init(pb, source)) { const char *err = strerror(errno); PurpleConversation *conv; @@ -685,37 +677,8 @@ close(source); bonjour_jabber_close_conversation(bb->conversation); bb->conversation = NULL; - - g_free(stream_start); - return; } - - bb->conversation->socket = source; - bb->conversation->rx_handler = purple_input_add(source, - PURPLE_INPUT_READ, _client_socket_handler, pb); - - /* This is unlikely to happen */ - if (ret < len) { - struct _stream_start_data *ss = g_new(struct _stream_start_data, 1); - ss->msg = g_strdup(stream_start + ret); - ss->tx_handler_cb = _send_data_write_cb; - bb->conversation->stream_data = ss; - /* Finish sending the stream start */ - bb->conversation->tx_handler = purple_input_add(source, - PURPLE_INPUT_WRITE, _start_stream, pb); - } - /* Process the send buffer */ - else { - bb->conversation->stream_started = TRUE; - /* Watch for when we can write the buffered messages */ - bb->conversation->tx_handler = purple_input_add(source, PURPLE_INPUT_WRITE, - _send_data_write_cb, pb); - /* We can probably write the data now. */ - _send_data_write_cb(pb, source, PURPLE_INPUT_WRITE); - } - - g_free(stream_start); } int @@ -809,7 +772,7 @@ /* Close the socket and remove the watcher */ if (bconv->socket >= 0) { /* Send the end of the stream to the other end of the conversation */ - if (bconv->stream_started) + if (bconv->sent_stream_start) send(bconv->socket, STREAM_END, strlen(STREAM_END), 0); /* TODO: We're really supposed to wait for "</stream:stream>" before closing the socket */ close(bconv->socket); @@ -828,6 +791,10 @@ g_free(ss->msg); g_free(ss); } + + if (bconv->context != NULL) + bonjour_parser_setup(bconv); + g_free(bconv); } }
--- a/libpurple/protocols/bonjour/jabber.h Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/bonjour/jabber.h Sun Aug 12 20:48:34 2007 +0000 @@ -26,6 +26,10 @@ #ifndef _BONJOUR_JABBER_H_ #define _BONJOUR_JABBER_H_ +#include <libxml/parser.h> + +#include "xmlnode.h" + #include "account.h" #include "circbuffer.h" @@ -43,9 +47,12 @@ guint rx_handler; guint tx_handler; PurpleCircBuffer *tx_buf; - gboolean stream_started; + gboolean sent_stream_start; + gboolean recv_stream_start; PurpleProxyConnectData *connect_data; gpointer stream_data; + xmlParserCtxt *context; + xmlnode *current; } BonjourJabberConversation; /** @@ -60,6 +67,12 @@ void bonjour_jabber_close_conversation(BonjourJabberConversation *bconv); +void bonjour_jabber_stream_started(PurpleBuddy *pb); + +void bonjour_jabber_stream_ended(PurpleBuddy *pb); + +void bonjour_jabber_process_packet(PurpleBuddy *pb, xmlnode *packet); + void bonjour_jabber_stop(BonjourJabber *data); #endif /* _BONJOUR_JABBER_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/bonjour/mdns_avahi.c Sun Aug 12 20:48:34 2007 +0000 @@ -0,0 +1,481 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "internal.h" + +#include "mdns_interface.h" +#include "debug.h" +#include "buddy.h" +#include "bonjour.h" + +#include <avahi-client/client.h> +#include <avahi-client/lookup.h> +#include <avahi-client/publish.h> + +#include <avahi-common/address.h> +#include <avahi-common/malloc.h> +#include <avahi-common/error.h> +#include <avahi-common/strlst.h> + +#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, + const char *host_name, const AvahiAddress *a, uint16_t port, AvahiStringList *txt, + AvahiLookupResultFlags flags, void *userdata) { + + BonjourBuddy *buddy; + PurpleAccount *account = userdata; + AvahiStringList *l; + size_t size; + char *key, *value; + int ret; + + g_return_if_fail(r != NULL); + + switch (event) { + 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); + + 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); + l = l->next; + if (ret < 0) + continue; + set_bonjour_buddy_value(buddy, key, value, size); + /* TODO: Since we're using the glib allocator, I think we + * can use the values instead of re-copying them */ + avahi_free(key); + avahi_free(value); + } + + if (!bonjour_buddy_check(buddy)) + bonjour_buddy_delete(buddy); + else + /* Add or update the buddy in our buddy list */ + bonjour_buddy_add_to_purple(buddy); + + break; + default: + purple_debug_info("bonjour", "Unrecognized Service Resolver event: %d.\n", event); + } + +} + +static void +_browser_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, + AvahiProtocol protocol, AvahiBrowserEvent event, + const char *name, const char *type, const char *domain, + AvahiLookupResultFlags flags, void *userdata) { + + PurpleAccount *account = userdata; + PurpleBuddy *gb = NULL; + + switch (event) { + case AVAHI_BROWSER_FAILURE: + purple_debug_error("bonjour", "_browser_callback - Failure: %s\n", + avahi_strerror(avahi_client_errno(avahi_service_browser_get_client(b)))); + /* TODO: This is an error that should be handled. */ + break; + case AVAHI_BROWSER_NEW: + /* A new peer has joined the network and uses iChat bonjour */ + purple_debug_info("bonjour", "_browser_callback - new service\n"); + /* Make sure it isn't us */ + if (g_ascii_strcasecmp(name, account->username) != 0) { + if (!avahi_service_resolver_new(avahi_service_browser_get_client(b), + interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, + 0, _resolver_callback, account)) { + purple_debug_warning("bonjour", "_browser_callback -- Error initiating resolver: %s\n", + avahi_strerror(avahi_client_errno(avahi_service_browser_get_client(b)))); + } + } + break; + case AVAHI_BROWSER_REMOVE: + purple_debug_info("bonjour", "_browser_callback - Remove service\n"); + gb = purple_find_buddy(account, name); + if (gb != NULL) { + bonjour_buddy_delete(gb->proto_data); + purple_blist_remove_buddy(gb); + } + break; + case AVAHI_BROWSER_ALL_FOR_NOW: + case AVAHI_BROWSER_CACHE_EXHAUSTED: + purple_debug_warning("bonjour", "(Browser) %s\n", + event == AVAHI_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW"); + break; + default: + purple_debug_info("bonjour", "Unrecognized Service browser event: %d.\n", event); + } +} + +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; + + g_return_if_fail(g == idata->group || idata->group == NULL); + + switch(state) { + case AVAHI_ENTRY_GROUP_ESTABLISHED: + purple_debug_info("bonjour", "Successfully registered service.\n"); + break; + case AVAHI_ENTRY_GROUP_COLLISION: + purple_debug_error("bonjour", "Collision registering entry group.\n"); + /* TODO: Handle error - this should log out the account. (Possibly with "wants to die")*/ + break; + case AVAHI_ENTRY_GROUP_FAILURE: + purple_debug_error("bonjour", "Error registering entry group: %s\n.", + avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g)))); + /* TODO: Handle error - this should log out the account.*/ + break; + case AVAHI_ENTRY_GROUP_UNCOMMITED: + case AVAHI_ENTRY_GROUP_REGISTERING: + break; + } + +} + +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 * + ****************************/ + +gboolean _mdns_init_session(BonjourDnsSd *data) { + AvahiSessionImplData *idata = g_new0(AvahiSessionImplData, 1); + const AvahiPoll *poll_api; + int error; + + /* Tell avahi to use g_malloc and g_free */ + avahi_set_allocator (avahi_glib_allocator ()); + + /* This currently depends on the glib mainloop, + * we should make it use the libpurple abstraction */ + + idata->glib_poll = avahi_glib_poll_new(NULL, G_PRIORITY_DEFAULT); + + poll_api = avahi_glib_poll_get(idata->glib_poll); + + idata->client = avahi_client_new(poll_api, 0, NULL, data, &error); + + if (idata->client == NULL) { + purple_debug_error("bonjour", "Error initializing Avahi: %s", avahi_strerror(error)); + avahi_glib_poll_free(idata->glib_poll); + g_free(idata); + return FALSE; + } + + data->mdns_impl_data = idata; + + return TRUE; +} + +gboolean _mdns_publish(BonjourDnsSd *data, PublishType type, GSList *records) { + int publish_result = 0; + AvahiSessionImplData *idata = data->mdns_impl_data; + AvahiStringList *lst = NULL; + + g_return_val_if_fail(idata != NULL, FALSE); + + if (!idata->group) { + idata->group = avahi_entry_group_new(idata->client, + _entry_group_cb, idata); + if (!idata->group) { + purple_debug_error("bonjour", + "Unable to initialize the data for the mDNS (%s).\n", + avahi_strerror(avahi_client_errno(idata->client))); + return FALSE; + } + } + + 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) { + case PUBLISH_START: + publish_result = avahi_entry_group_add_service_strlst( + idata->group, AVAHI_IF_UNSPEC, + AVAHI_PROTO_UNSPEC, 0, + purple_account_get_username(data->account), + ICHAT_SERVICE, NULL, NULL, data->port_p2pj, lst); + break; + case PUBLISH_UPDATE: + publish_result = avahi_entry_group_update_service_txt_strlst( + idata->group, AVAHI_IF_UNSPEC, + AVAHI_PROTO_UNSPEC, 0, + purple_account_get_username(data->account), + ICHAT_SERVICE, NULL, lst); + break; + } + + /* Free the memory used by temp data */ + avahi_string_list_free(lst); + + if (publish_result < 0) { + purple_debug_error("bonjour", + "Failed to add the " ICHAT_SERVICE " service. Error: %s\n", + avahi_strerror(publish_result)); + return FALSE; + } + + 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)); + return FALSE; + } + + return TRUE; +} + +gboolean _mdns_browse(BonjourDnsSd *data) { + AvahiSessionImplData *idata = data->mdns_impl_data; + + g_return_val_if_fail(idata != NULL, FALSE); + + idata->sb = avahi_service_browser_new(idata->client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, ICHAT_SERVICE, NULL, 0, _browser_callback, data->account); + if (!idata->sb) { + + purple_debug_error("bonjour", + "Unable to initialize service browser. Error: %s\n.", + avahi_strerror(avahi_client_errno(idata->client))); + return FALSE; + } + + return TRUE; +} + +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) { + AvahiSessionImplData *idata = data->mdns_impl_data; + + if (idata == NULL || idata->client == NULL) + return; + + if (idata->sb != NULL) + avahi_service_browser_free(idata->sb); + + avahi_client_free(idata->client); + avahi_glib_poll_free(idata->glib_poll); + + g_free(idata); + + data->mdns_impl_data = NULL; +} + +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 _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 Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/bonjour/mdns_common.c Sun Aug 12 20:48:34 2007 +0000 @@ -17,30 +17,27 @@ #include <string.h> #include "internal.h" -#include "config.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,76 +138,86 @@ 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) -{ - PurpleAccount *account; - PurpleConnection *gc; - gint dns_sd_socket; - gpointer opaque_data; - -#ifdef USE_BONJOUR_HOWL - sw_discovery_oid session_id; -#endif - - account = data->account; - gc = purple_account_get_connection(account); +gboolean bonjour_dns_sd_start(BonjourDnsSd *data) { /* Initialize the dns-sd data and session */ -#ifndef USE_BONJOUR_APPLE - if (sw_discovery_init(&data->session) != SW_OKAY) - { - purple_debug_error("bonjour", "Unable to initialize an mDNS session.\n"); - - /* In Avahi, sw_discovery_init frees data->session but doesn't clear it */ - data->session = NULL; - + if (!_mdns_init_session(data)) return FALSE; - } -#endif /* Publish our bonjour IM client at the mDNS daemon */ - - if (0 != _mdns_publish(data, PUBLISH_START)) - { + if (!publish_presence(data, PUBLISH_START)) return FALSE; - } /* Advise the daemon that we are waiting for connections */ - -#ifdef USE_BONJOUR_APPLE - if (DNSServiceBrowse(&data->browser, 0, 0, ICHAT_SERVICE, NULL, _mdns_service_browse_callback, account) - != kDNSServiceErr_NoError) -#else /* USE_BONJOUR_HOWL */ - if (sw_discovery_browse(data->session, 0, ICHAT_SERVICE, NULL, _browser_reply, - account, &session_id) != SW_OKAY) -#endif - { + if (!_mdns_browse(data)) { purple_debug_error("bonjour", "Unable to get service."); return FALSE; } - /* Get the socket that communicates with the mDNS daemon and bind it to a */ - /* callback that will handle the dns_sd packets */ - -#ifdef USE_BONJOUR_APPLE - dns_sd_socket = DNSServiceRefSockFD(data->browser); - opaque_data = data->browser; -#else /* USE_BONJOUR_HOWL */ - dns_sd_socket = sw_discovery_socket(data->session); - opaque_data = data->session; -#endif - - gc->inpa = purple_input_add(dns_sd_socket, PURPLE_INPUT_READ, - _mdns_handle_event, opaque_data); - return TRUE; } @@ -140,37 +225,6 @@ * Unregister the "_presence._tcp" service at the mDNS daemon. */ -void -bonjour_dns_sd_stop(BonjourDnsSd *data) -{ - PurpleAccount *account; - PurpleConnection *gc; - -#ifdef USE_BONJOUR_APPLE - if (data->advertisement == NULL || data->browser == NULL) -#else /* USE_BONJOUR_HOWL */ - if (data->session == NULL) -#endif - return; - -#ifdef USE_BONJOUR_HOWL - sw_discovery_cancel(data->session, data->session_id); -#endif - - account = data->account; - gc = purple_account_get_connection(account); - purple_input_remove(gc->inpa); - -#ifdef USE_BONJOUR_APPLE - /* hack: for win32, we need to stop listening to the advertisement pipe too */ - purple_input_remove(data->advertisement_handler); - - DNSServiceRefDeallocate(data->advertisement); - DNSServiceRefDeallocate(data->browser); - data->advertisement = NULL; - data->browser = NULL; -#else /* USE_BONJOUR_HOWL */ - g_free(data->session); - data->session = NULL; -#endif +void bonjour_dns_sd_stop(BonjourDnsSd *data) { + _mdns_stop(data); }
--- a/libpurple/protocols/bonjour/mdns_common.h Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/bonjour/mdns_common.h Sun Aug 12 20:48:34 2007 +0000 @@ -19,11 +19,7 @@ #include "mdns_types.h" -#ifdef USE_BONJOUR_APPLE -#include "mdns_win32.h" -#elif defined USE_BONJOUR_HOWL -#include "mdns_howl.h" -#endif +#include "buddy.h" /** * Allocate space for the dns-sd data. @@ -41,6 +37,16 @@ void bonjour_dns_sd_send_status(BonjourDnsSd *data, const char *status, const char *status_message); /** + * Retrieve the buddy icon blob + */ +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 Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/bonjour/mdns_howl.c Sun Aug 12 20:48:34 2007 +0000 @@ -14,12 +14,22 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#include "mdns_howl.h" +#include "internal.h" +#include "mdns_interface.h" #include "debug.h" #include "buddy.h" -sw_result HOWL_API +#include <howl.h> + +/* data used by howl bonjour implementation */ +typedef struct _howl_impl_data { + sw_discovery session; + sw_discovery_oid session_id; + guint session_handler; +} HowlSessionImplData; + +static sw_result HOWL_API _publish_reply(sw_discovery discovery, sw_discovery_oid oid, sw_discovery_publish_status status, sw_opaque extra) { @@ -45,7 +55,7 @@ return SW_OKAY; } -sw_result HOWL_API +static sw_result HOWL_API _resolve_reply(sw_discovery discovery, sw_discovery_oid oid, sw_uint32 interface_index, sw_const_string name, sw_const_string type, sw_const_string domain, @@ -75,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); @@ -94,7 +105,7 @@ return SW_OKAY; } -sw_result HOWL_API +static sw_result HOWL_API _browser_reply(sw_discovery discovery, sw_discovery_oid oid, sw_discovery_browse_status status, sw_uint32 interface_index, sw_const_string name, @@ -136,7 +147,7 @@ break; case SW_DISCOVERY_BROWSE_REMOVE_SERVICE: purple_debug_info("bonjour", "_browser_reply --> Remove service\n"); - gb = purple_find_buddy((PurpleAccount*)extra, name); + gb = purple_find_buddy(account, name); if (gb != NULL) { bonjour_buddy_delete(gb->proto_data); @@ -153,86 +164,125 @@ return SW_OKAY; } -int -_mdns_publish(BonjourDnsSd *data, PublishType type) +static void +_mdns_handle_event(gpointer data, gint source, PurpleInputCondition condition) { - sw_text_record dns_data; - sw_result publish_result = SW_OKAY; - char portstring[6]; - const char *jid, *aim, *email; + sw_discovery_read_socket((sw_discovery)data); +} + +/**************************** + * mdns_interface functions * + ****************************/ + +gboolean _mdns_init_session(BonjourDnsSd *data) { + HowlSessionImplData *idata = g_new0(HowlSessionImplData, 1); - /* Fill the data for the service */ - if (sw_text_record_init(&dns_data) != SW_OKAY) - { - purple_debug_error("bonjour", "Unable to initialize the data for the mDNS.\n"); - return -1; + if (sw_discovery_init(&idata->session) != SW_OKAY) { + purple_debug_error("bonjour", "Unable to initialize an mDNS session.\n"); + + /* In Avahi, sw_discovery_init frees data->session but doesn't clear it */ + idata->session = NULL; + + g_free(idata); + + return FALSE; } - /* Convert the port to a string */ - snprintf(portstring, sizeof(portstring), "%d", data->port_p2pj); + data->mdns_impl_data = idata; + + return TRUE; +} - 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 - */ +gboolean _mdns_publish(BonjourDnsSd *data, PublishType type, GSList *records) { + sw_text_record dns_data; + sw_result publish_result = SW_OKAY; + HowlSessionImplData *idata = data->mdns_impl_data; - /* 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); + g_return_val_if_fail(idata != NULL, FALSE); - /* TODO: ext, nick, node */ + /* Fill the data for the service */ + if (sw_text_record_init(&dns_data) != SW_OKAY) { + purple_debug_error("bonjour", "Unable to initialize the data for the mDNS.\n"); + return FALSE; + } + + 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) - { + switch (type) { case PUBLISH_START: - publish_result = sw_discovery_publish(data->session, 0, purple_account_get_username(data->account), ICHAT_SERVICE, NULL, + publish_result = sw_discovery_publish(idata->session, 0, purple_account_get_username(data->account), ICHAT_SERVICE, NULL, NULL, data->port_p2pj, sw_text_record_bytes(dns_data), sw_text_record_len(dns_data), - _publish_reply, NULL, &data->session_id); + _publish_reply, NULL, &idata->session_id); break; case PUBLISH_UPDATE: - publish_result = sw_discovery_publish_update(data->session, data->session_id, + publish_result = sw_discovery_publish_update(idata->session, idata->session_id, sw_text_record_bytes(dns_data), sw_text_record_len(dns_data)); break; } - if (publish_result != SW_OKAY) - { - purple_debug_error("bonjour", "Unable to publish or change the status of the _presence._tcp service.\n"); - return -1; - } /* Free the memory used by temp data */ sw_text_record_fina(dns_data); - return 0; + if (publish_result != SW_OKAY) { + purple_debug_error("bonjour", "Unable to publish or change the status of the " ICHAT_SERVICE " service.\n"); + return FALSE; + } + + return TRUE; +} + +gboolean _mdns_browse(BonjourDnsSd *data) { + HowlSessionImplData *idata = data->mdns_impl_data; + /* TODO: don't we need to hang onto this to cancel later? */ + sw_discovery_oid session_id; + + g_return_val_if_fail(idata != NULL, FALSE); + + 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; +} + +gboolean _mdns_set_buddy_icon_data(BonjourDnsSd *data, gconstpointer avatar_data, gsize avatar_len) { + return FALSE; } -void -_mdns_handle_event(gpointer data, gint source, PurpleInputCondition condition) -{ - sw_discovery_read_socket((sw_discovery)data); +void _mdns_stop(BonjourDnsSd *data) { + HowlSessionImplData *idata = data->mdns_impl_data; + + if (idata == NULL || idata->session == NULL) + return; + + 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); + + g_free(idata); + + data->mdns_impl_data = NULL; } + +void _mdns_init_buddy(BonjourBuddy *buddy) { +} + +void _mdns_delete_buddy(BonjourBuddy *buddy) { +} + +void _mdns_retrieve_buddy_icon(BonjourBuddy* buddy) { +} + +
--- a/libpurple/protocols/bonjour/mdns_howl.h Sun Aug 12 04:03:45 2007 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,47 +0,0 @@ -/* - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Library General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ - -#ifndef _BONJOUR_MDNS_HOWL -#define _BONJOUR_MDNS_HOWL - -#include "config.h" - -#ifdef USE_BONJOUR_HOWL - -#include <howl.h> -#include <glib.h> -#include "mdns_types.h" - -/* callback functions */ - -sw_result HOWL_API _publish_reply(sw_discovery discovery, sw_discovery_oid oid, sw_discovery_publish_status status, sw_opaque extra); - -sw_result HOWL_API _resolve_reply(sw_discovery discovery, sw_discovery_oid oid, sw_uint32 interface_index, sw_const_string name, - sw_const_string type, sw_const_string domain, sw_ipv4_address address, sw_port port, sw_octets text_record, - sw_ulong text_record_len, sw_opaque extra); - -sw_result HOWL_API _browser_reply(sw_discovery discovery, sw_discovery_oid oid, sw_discovery_browse_status status, - sw_uint32 interface_index, sw_const_string name, sw_const_string type, sw_const_string domain, sw_opaque_t extra); - - -/* interface functions */ - -int _mdns_publish(BonjourDnsSd *data, PublishType type); -void _mdns_handle_event(gpointer data, gint source, PurpleInputCondition condition); - -#endif - -#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/bonjour/mdns_interface.h Sun Aug 12 20:48:34 2007 +0000 @@ -0,0 +1,39 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _BONJOUR_MDNS_INTERFACE +#define _BONJOUR_MDNS_INTERFACE + +#include "mdns_types.h" +#include "buddy.h" + +gboolean _mdns_init_session(BonjourDnsSd *data); + +gboolean _mdns_publish(BonjourDnsSd *data, PublishType type, GSList *records); + +gboolean _mdns_browse(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); + +void _mdns_retrieve_buddy_icon(BonjourBuddy* buddy); + +#endif
--- a/libpurple/protocols/bonjour/mdns_types.h Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/bonjour/mdns_types.h Sun Aug 12 20:48:34 2007 +0000 @@ -19,31 +19,14 @@ #include <glib.h> #include "account.h" -#include "config.h" - -#ifdef USE_BONJOUR_APPLE -#include "dns_sd_proxy.h" -#else /* USE_BONJOUR_HOWL */ -#include <howl.h> -#endif #define ICHAT_SERVICE "_presence._tcp." /** * Data to be used by the dns-sd connection. */ -typedef struct _BonjourDnsSd -{ -#ifdef USE_BONJOUR_APPLE - DNSServiceRef advertisement; - DNSServiceRef browser; - - int advertisement_handler; /* hack... windows bonjour is broken, so we have to have this */ -#else /* USE_BONJOUR_HOWL */ - sw_discovery session; - sw_discovery_oid session_id; -#endif - +typedef struct _BonjourDnsSd { + gpointer mdns_impl_data; PurpleAccount *account; gchar *first; gchar *last; @@ -59,5 +42,4 @@ PUBLISH_UPDATE } PublishType; - #endif
--- a/libpurple/protocols/bonjour/mdns_win32.c Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/bonjour/mdns_win32.c Sun Aug 12 20:48:34 2007 +0000 @@ -15,22 +15,48 @@ */ #include "internal.h" -#include "mdns_win32.h" +#include "debug.h" -#include "debug.h" +#include "buddy.h" +#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 -{ +typedef struct _ResolveCallbackArgs { DNSServiceRef resolver; - int resolver_fd; + guint resolver_handler; + gchar *full_service_name; PurpleDnsQueryData *query; - gchar *fqn; BonjourBuddy* buddy; } ResolveCallbackArgs; +/* data used by win32 bonjour implementation */ +typedef struct _win32_session_impl_data { + DNSServiceRef presence_svc; + DNSServiceRef browser_svc; + DNSRecordRef buddy_icon_rec; + + guint presence_handler; + guint browser_handler; +} Win32SessionImplData; + +typedef struct _win32_buddy_impl_data { + DNSServiceRef txt_query; + guint txt_query_handler; + DNSServiceRef null_query; + guint null_query_handler; +} Win32BuddyImplData; + +static void +_mdns_handle_event(gpointer data, gint source, PurpleInputCondition condition) { + DNSServiceProcessResult((DNSServiceRef) data); +} + static void _mdns_parse_text_record(BonjourBuddy* buddy, const char* record, uint16_t record_len) { @@ -38,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) @@ -46,18 +73,36 @@ } 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) { + if (kDNSServiceErr_NoError != errorCode) - purple_debug_error("bonjour", "text record query - callback error.\n"); + purple_debug_error("bonjour", "record query - callback error.\n"); else if (flags & kDNSServiceFlagsAdd) { - BonjourBuddy *buddy = (BonjourBuddy*)context; - _mdns_parse_text_record(buddy, rdata, rdlen); - bonjour_buddy_add_to_purple(buddy); + if (rrtype == kDNSServiceType_TXT) { + /* New Buddy */ + BonjourBuddy *buddy = (BonjourBuddy*) context; + _mdns_parse_text_record(buddy, rdata, rdlen); + bonjour_buddy_add_to_purple(buddy); + } else if (rrtype == kDNSServiceType_NULL) { + /* Buddy Icon response */ + BonjourBuddy *buddy = (BonjourBuddy*) context; + Win32BuddyImplData *idata = buddy->mdns_impl_data; + + g_return_if_fail(idata != NULL); + + bonjour_buddy_got_buddy_icon(buddy, rdata, rdlen); + + /* We've got what we need; stop listening */ + purple_input_remove(idata->null_query_handler); + idata->null_query_handler = -1; + DNSServiceRefDeallocate(idata->null_query); + idata->null_query = NULL; + } } } @@ -68,24 +113,28 @@ if (!hosts || !hosts->data) purple_debug_error("bonjour", "host resolution - callback error.\n"); - else - { + else { struct sockaddr_in *addr = (struct sockaddr_in*)g_slist_nth_data(hosts, 1); BonjourBuddy* buddy = args->buddy; + Win32BuddyImplData *idata = buddy->mdns_impl_data; + + g_return_if_fail(idata != NULL); buddy->ip = g_strdup(inet_ntoa(addr->sin_addr)); /* finally, set up the continuous txt record watcher, and add the buddy to purple */ - if (kDNSServiceErr_NoError == DNSServiceQueryRecord(&buddy->txt_query, 0, 0, args->fqn, - kDNSServiceType_TXT, kDNSServiceClass_IN, _mdns_text_record_query_callback, buddy)) - { - gint fd = DNSServiceRefSockFD(buddy->txt_query); - buddy->txt_query_fd = purple_input_add(fd, PURPLE_INPUT_READ, _mdns_handle_event, buddy->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); - } - else + } else bonjour_buddy_delete(buddy); } @@ -95,7 +144,7 @@ /* free the remaining args memory */ purple_dnsquery_destroy(args->query); - g_free(args->fqn); + g_free(args->full_service_name); g_free(args); } @@ -106,7 +155,7 @@ ResolveCallbackArgs *args = (ResolveCallbackArgs*)context; /* remove the input fd and destroy the service ref */ - purple_input_remove(args->resolver_fd); + purple_input_remove(args->resolver_handler); DNSServiceRefDeallocate(args->resolver); if (kDNSServiceErr_NoError != errorCode) @@ -123,14 +172,14 @@ _mdns_parse_text_record(args->buddy, txtRecord, txtLen); /* set more arguments, and start the host resolver */ - args->fqn = 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->fqn); + g_free(args->full_service_name); g_free(args); } } @@ -139,16 +188,16 @@ 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"); } -void DNSSD_API +static void DNSSD_API _mdns_service_browse_callback(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char *serviceName, const char *regtype, const char *replyDomain, void *context) { @@ -157,131 +206,93 @@ if (kDNSServiceErr_NoError != errorCode) purple_debug_error("bonjour", "service browser - callback error"); - else if (flags & kDNSServiceFlagsAdd) - { + else if (flags & kDNSServiceFlagsAdd) { /* A presence service instance has been discovered... check it isn't us! */ - if (g_ascii_strcasecmp(serviceName, account->username) != 0) - { + if (g_ascii_strcasecmp(serviceName, account->username) != 0) { /* OK, lets go ahead and resolve it to add to the buddy list */ ResolveCallbackArgs *args = g_new0(ResolveCallbackArgs, 1); args->buddy = bonjour_buddy_new(serviceName, account); - if (kDNSServiceErr_NoError != DNSServiceResolve(&args->resolver, 0, 0, serviceName, regtype, replyDomain, _mdns_service_resolve_callback, args)) - { + if (kDNSServiceErr_NoError != DNSServiceResolve(&args->resolver, 0, 0, serviceName, regtype, replyDomain, _mdns_service_resolve_callback, args)) { bonjour_buddy_delete(args->buddy); g_free(args); purple_debug_error("bonjour", "service browser - failed to resolve service.\n"); - } - else - { + } else { /* get a file descriptor for this service ref, and add it to the input list */ - gint resolver_fd = DNSServiceRefSockFD(args->resolver); - args->resolver_fd = purple_input_add(resolver_fd, PURPLE_INPUT_READ, _mdns_handle_event, args->resolver); + gint fd = DNSServiceRefSockFD(args->resolver); + args->resolver_handler = purple_input_add(fd, PURPLE_INPUT_READ, _mdns_handle_event, args->resolver); } } - } - else - { + } else { /* A peer has sent a goodbye packet, remove them from the buddy list */ purple_debug_info("bonjour", "service browser - remove notification\n"); gb = purple_find_buddy(account, serviceName); - if (gb != NULL) - { + if (gb != NULL) { bonjour_buddy_delete(gb->proto_data); purple_blist_remove_buddy(gb); } } } -int -_mdns_publish(BonjourDnsSd *data, PublishType type) -{ +/**************************** + * mdns_interface functions * + ****************************/ + +gboolean _mdns_init_session(BonjourDnsSd *data) { + data->mdns_impl_data = g_new0(Win32SessionImplData, 1); + return TRUE; +} + +gboolean _mdns_publish(BonjourDnsSd *data, PublishType type, GSList *records) { TXTRecordRef dns_data; - char portstring[6]; - int ret = 0; - const char *jid, *aim, *email; - DNSServiceErrorType set_ret; + gboolean ret = TRUE; + 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 - */ + 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; + } - /* 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 */ - - if (set_ret != kDNSServiceErr_NoError) - { + if (set_ret != kDNSServiceErr_NoError) { purple_debug_error("bonjour", "Unable to allocate memory for text record.\n"); - ret = -1; - } - else - { + ret = FALSE; + } else { DNSServiceErrorType err = kDNSServiceErr_NoError; /* OK, we're done constructing the text record, (re)publish the service */ - switch (type) - { + switch (type) { case PUBLISH_START: - err = DNSServiceRegister(&data->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(data->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; } - if (kDNSServiceErr_NoError != err) - { + if (err != kDNSServiceErr_NoError) { purple_debug_error("bonjour", "Failed to publish presence service.\n"); - ret = -1; - } - else if (PUBLISH_START == type) - { - /* hack: Bonjour on windows is broken. We don't care about the callback but we have to listen anyway */ - gint advertisement_fd = DNSServiceRefSockFD(data->advertisement); - data->advertisement_handler = purple_input_add(advertisement_fd, PURPLE_INPUT_READ, _mdns_handle_event, data->advertisement); + ret = FALSE; + } else if (type == PUBLISH_START) { + /* 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); } } @@ -290,8 +301,113 @@ return ret; } -void -_mdns_handle_event(gpointer data, gint source, PurpleInputCondition condition) -{ - DNSServiceProcessResult((DNSServiceRef)data); +gboolean _mdns_browse(BonjourDnsSd *data) { + Win32SessionImplData *idata = data->mdns_impl_data; + + g_return_val_if_fail(idata != NULL, FALSE); + + if (DNSServiceBrowse(&idata->browser_svc, 0, 0, ICHAT_SERVICE, NULL, + _mdns_service_browse_callback, data->account) + == kDNSServiceErr_NoError) { + idata->browser_handler = purple_input_add(DNSServiceRefSockFD(idata->browser_svc), + PURPLE_INPUT_READ, _mdns_handle_event, idata->browser_svc); + return TRUE; + } + + return FALSE; +} + +void _mdns_stop(BonjourDnsSd *data) { + Win32SessionImplData *idata = data->mdns_impl_data; + + if (idata == NULL) + return; + + if (idata->presence_svc != NULL) { + purple_input_remove(idata->presence_handler); + DNSServiceRefDeallocate(idata->presence_svc); + } + + 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); +} + +void _mdns_delete_buddy(BonjourBuddy *buddy) { + Win32BuddyImplData *idata = buddy->mdns_impl_data; + + g_return_if_fail(idata != NULL); + + if (idata->txt_query != NULL) { + purple_input_remove(idata->txt_query_handler); + DNSServiceRefDeallocate(idata->txt_query); + } + + if (idata->null_query != NULL) { + purple_input_remove(idata->null_query_handler); + DNSServiceRefDeallocate(idata->null_query); + } + + g_free(idata); + + buddy->mdns_impl_data = NULL; +} + +void _mdns_retrieve_buddy_icon(BonjourBuddy* buddy) { + Win32BuddyImplData *idata = buddy->mdns_impl_data; + char svc_name[kDNSServiceMaxDomainName]; + + g_return_if_fail(idata != NULL); + + /* Cancel any existing query */ + if (idata->null_query != NULL) { + purple_input_remove(idata->null_query_handler); + idata->null_query_handler = 0; + DNSServiceRefDeallocate(idata->null_query); + idata->null_query = NULL; + } + + 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/bonjour/mdns_win32.h Sun Aug 12 04:03:45 2007 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,40 +0,0 @@ -/* - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Library General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ - -#ifndef _BONJOUR_MDNS_WIN32 -#define _BONJOUR_MDNS_WIN32 - -#ifdef USE_BONJOUR_APPLE - -#include <glib.h> -#include "mdns_types.h" -#include "buddy.h" -#include "dnsquery.h" -#include "dns_sd_proxy.h" - -/* Bonjour async callbacks */ - -void DNSSD_API _mdns_service_browse_callback(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, - DNSServiceErrorType errorCode, const char *serviceName, const char *regtype, const char *replyDomain, void *context); - -/* interface functions */ - -int _mdns_publish(BonjourDnsSd *data, PublishType type); -void _mdns_handle_event(gpointer data, gint source, PurpleInputCondition condition); - -#endif - -#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/bonjour/parser.c Sun Aug 12 20:48:34 2007 +0000 @@ -0,0 +1,194 @@ +/* + * purple - Bonjour Jabber XML parser stuff + * + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include "internal.h" + +#include <libxml/parser.h> + +#include "connection.h" +#include "debug.h" +#include "jabber.h" +#include "parser.h" +#include "util.h" +#include "xmlnode.h" + +static void +bonjour_parser_element_start_libxml(void *user_data, + const xmlChar *element_name, const xmlChar *prefix, const xmlChar *namespace, + int nb_namespaces, const xmlChar **namespaces, + int nb_attributes, int nb_defaulted, const xmlChar **attributes) +{ + PurpleBuddy *pb = user_data; + BonjourBuddy *bb = pb->proto_data; + BonjourJabberConversation *bconv = bb->conversation; + + xmlnode *node; + int i; + + if(!element_name) { + return; + } else if(!xmlStrcmp(element_name, (xmlChar*) "stream")) { + bconv->recv_stream_start = TRUE; + bonjour_jabber_stream_started(pb); + } else { + + if(bconv->current) + node = xmlnode_new_child(bconv->current, (const char*) element_name); + else + node = xmlnode_new((const char*) element_name); + xmlnode_set_namespace(node, (const char*) namespace); + + for(i=0; i < nb_attributes * 5; i+=5) { + char *txt; + int attrib_len = attributes[i+4] - attributes[i+3]; + char *attrib = g_malloc(attrib_len + 1); + char *attrib_ns = NULL; + + if (attributes[i+2]) { + attrib_ns = g_strdup((char*)attributes[i+2]);; + } + + memcpy(attrib, attributes[i+3], attrib_len); + attrib[attrib_len] = '\0'; + + txt = attrib; + attrib = purple_unescape_html(txt); + g_free(txt); + xmlnode_set_attrib_with_namespace(node, (const char*) attributes[i], attrib_ns, attrib); + g_free(attrib); + g_free(attrib_ns); + } + + bconv->current = node; + } +} + +static void +bonjour_parser_element_end_libxml(void *user_data, const xmlChar *element_name, + const xmlChar *prefix, const xmlChar *namespace) +{ + PurpleBuddy *pb = user_data; + BonjourBuddy *bb = pb->proto_data; + BonjourJabberConversation *bconv = bb->conversation; + + if(!bconv->current) { + /* We don't keep a reference to the start stream xmlnode, + * so we have to check for it here to close the conversation */ + if(!xmlStrcmp(element_name, (xmlChar*) "stream")) { + bonjour_jabber_stream_ended(pb); + } + return; + } + + if(bconv->current->parent) { + if(!xmlStrcmp((xmlChar*) bconv->current->name, element_name)) + bconv->current = bconv->current->parent; + } else { + xmlnode *packet = bconv->current; + bconv->current = NULL; + bonjour_jabber_process_packet(pb, packet); + xmlnode_free(packet); + } +} + +static void +bonjour_parser_element_text_libxml(void *user_data, const xmlChar *text, int text_len) +{ + PurpleBuddy *pb = user_data; + BonjourBuddy *bb = pb->proto_data; + BonjourJabberConversation *bconv = bb->conversation; + + if(!bconv->current) + return; + + if(!text || !text_len) + return; + + xmlnode_insert_data(bconv->current, (const char*) text, text_len); +} + +static xmlSAXHandler bonjour_parser_libxml = { + .internalSubset = NULL, + .isStandalone = NULL, + .hasInternalSubset = NULL, + .hasExternalSubset = NULL, + .resolveEntity = NULL, + .getEntity = NULL, + .entityDecl = NULL, + .notationDecl = NULL, + .attributeDecl = NULL, + .elementDecl = NULL, + .unparsedEntityDecl = NULL, + .setDocumentLocator = NULL, + .startDocument = NULL, + .endDocument = NULL, + .startElement = NULL, + .endElement = NULL, + .reference = NULL, + .characters = bonjour_parser_element_text_libxml, + .ignorableWhitespace = NULL, + .processingInstruction = NULL, + .comment = NULL, + .warning = NULL, + .error = NULL, + .fatalError = NULL, + .getParameterEntity = NULL, + .cdataBlock = NULL, + .externalSubset = NULL, + .initialized = XML_SAX2_MAGIC, + ._private = NULL, + .startElementNs = bonjour_parser_element_start_libxml, + .endElementNs = bonjour_parser_element_end_libxml, + .serror = NULL +}; + +void +bonjour_parser_setup(BonjourJabberConversation *bconv) +{ + + /* This seems backwards, but it makes sense. The libxml code creates + * the parser context when you try to use it (this way, it can figure + * out the encoding at creation time. So, setting up the parser is + * just a matter of destroying any current parser. */ + if (bconv->context) { + xmlParseChunk(bconv->context, NULL,0,1); + xmlFreeParserCtxt(bconv->context); + bconv->context = NULL; + } +} + + +void bonjour_parser_process(PurpleBuddy *pb, const char *buf, int len) +{ + BonjourBuddy *bb = pb->proto_data; + + if (bb->conversation->context == NULL) { + /* libxml inconsistently starts parsing on creating the + * parser, so do a ParseChunk right afterwards to force it. */ + bb->conversation->context = xmlCreatePushParserCtxt(&bonjour_parser_libxml, pb, buf, len, NULL); + xmlParseChunk(bb->conversation->context, "", 0, 0); + } else if (xmlParseChunk(bb->conversation->context, buf, len, 0) < 0) { + /* TODO: What should we do here - I assume we should display an error or something (maybe just print something to the conv?) */ + purple_debug_error("bonjour", "Error parsing xml.\n"); + } +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/bonjour/parser.h Sun Aug 12 20:48:34 2007 +0000 @@ -0,0 +1,33 @@ +/** + * @file parser.h Bonjour Jabber XML parser functions + * + * purple + * + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef _PURPLE_BONJOUR_PARSER_H_ +#define _PURPLE_BONJOUR_PARSER_H_ + +#include "buddy.h" +#include "jabber.h" + +void bonjour_parser_setup(BonjourJabberConversation *bconv); +void bonjour_parser_process(PurpleBuddy *pb, const char *buf, int len); + +#endif /* _PURPLE_BONJOUR_PARSER_H_ */
--- a/libpurple/protocols/jabber/auth.c Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/jabber/auth.c Sun Aug 12 20:48:34 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); @@ -682,11 +682,11 @@ gchar *a1, *convnode=NULL, *convpasswd = NULL, *ha1, *ha2, *kd, *x, *z; - if((convnode = g_convert(jid->node, strlen(jid->node), "iso-8859-1", "utf-8", + if((convnode = g_convert(jid->node, -1, "iso-8859-1", "utf-8", NULL, NULL, NULL)) == NULL) { convnode = g_strdup(jid->node); } - if(passwd && ((convpasswd = g_convert(passwd, strlen(passwd), "iso-8859-1", + if(passwd && ((convpasswd = g_convert(passwd, -1, "iso-8859-1", "utf-8", NULL, NULL, NULL)) == NULL)) { convpasswd = g_strdup(passwd); }
--- a/libpurple/protocols/jabber/google.c Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/jabber/google.c Sun Aug 12 20:48:34 2007 +0000 @@ -119,8 +119,8 @@ g_free(to_name); g_free(tos); g_free(froms); - for (; i >= 0; i--) - g_free(subjects[i]); + for (; i > 0; i--) + g_free(subjects[i - 1]); g_free(subjects); g_free(urls);
--- a/libpurple/protocols/jabber/jabber.c Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/jabber/jabber.c Sun Aug 12 20:48:34 2007 +0000 @@ -1709,7 +1709,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; @@ -1722,8 +1722,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/libpurple/protocols/jabber/presence.c Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/jabber/presence.c Sun Aug 12 20:48:34 2007 +0000 @@ -193,16 +193,18 @@ char *who; }; -static void authorize_add_cb(struct _jabber_add_permit *jap) +static void authorize_add_cb(gpointer data) { + struct _jabber_add_permit *jap = data; jabber_presence_subscription_set(jap->gc->proto_data, jap->who, "subscribed"); g_free(jap->who); g_free(jap); } -static void deny_add_cb(struct _jabber_add_permit *jap) +static void deny_add_cb(gpointer data) { + struct _jabber_add_permit *jap = data; jabber_presence_subscription_set(jap->gc->proto_data, jap->who, "unsubscribed"); @@ -305,7 +307,7 @@ jap->js = js; purple_account_request_authorization(purple_connection_get_account(js->gc), from, NULL, NULL, NULL, onlist, - G_CALLBACK(authorize_add_cb), G_CALLBACK(deny_add_cb), jap); + authorize_add_cb, deny_add_cb, jap); jabber_id_free(jid); return; } else if(type && !strcmp(type, "subscribed")) {
--- a/libpurple/protocols/jabber/xdata.c Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/jabber/xdata.c Sun Aug 12 20:48:34 2007 +0000 @@ -256,7 +256,7 @@ continue; if(!(lbl = xmlnode_get_attrib(optnode, "label"))) - label = value; + lbl = value; data->values = g_slist_prepend(data->values, value);
--- a/libpurple/protocols/msn/userlist.c Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/msn/userlist.c Sun Aug 12 20:48:34 2007 +0000 @@ -38,8 +38,9 @@ * Callbacks **************************************************************************/ static void -msn_accept_add_cb(MsnPermitAdd *pa) +msn_accept_add_cb(gpointer data) { + MsnPermitAdd *pa = data; MsnSession *session = pa->gc->proto_data; MsnUserList *userlist = session->userlist; @@ -51,8 +52,9 @@ } static void -msn_cancel_add_cb(MsnPermitAdd *pa) +msn_cancel_add_cb(gpointer data) { + MsnPermitAdd *pa = data; MsnSession *session = pa->gc->proto_data; MsnUserList *userlist = session->userlist; @@ -75,7 +77,7 @@ purple_account_request_authorization(purple_connection_get_account(gc), passport, NULL, friendly, NULL, purple_find_buddy(purple_connection_get_account(gc), passport) != NULL, - G_CALLBACK(msn_accept_add_cb), G_CALLBACK(msn_cancel_add_cb), pa); + msn_accept_add_cb, msn_cancel_add_cb, pa); } /**************************************************************************
--- a/libpurple/protocols/oscar/family_feedbag.c Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/oscar/family_feedbag.c Sun Aug 12 20:48:34 2007 +0000 @@ -695,18 +695,6 @@ cur = cur->next; } - /* Check if there are empty groups and delete them */ - cur = od->ssi.local; - while (cur) { - next = cur->next; - if (cur->type == AIM_SSI_TYPE_GROUP) { - aim_tlv_t *tlv = aim_tlv_gettlv(cur->data, 0x00c8, 1); - if (!tlv || !tlv->length) - aim_ssi_itemlist_del(&od->ssi.local, cur); - } - cur = next; - } - /* Check if the master group is empty */ if ((cur = aim_ssi_itemlist_find(od->ssi.local, 0x0000, 0x0000)) && (!cur->data)) aim_ssi_itemlist_del(&od->ssi.local, cur); @@ -841,18 +829,39 @@ /* Modify the parent group */ aim_ssi_itemlist_rebuildgroup(od->ssi.local, group); - /* Check if we should delete the parent group */ - if ((del = aim_ssi_itemlist_finditem(od->ssi.local, group, NULL, AIM_SSI_TYPE_GROUP)) && (!del->data)) { - aim_ssi_itemlist_del(&od->ssi.local, del); + /* Sync our local list with the server list */ + return aim_ssi_sync(od); +} + +/** + * Deletes a group from the list. + * + * @param od The oscar odion. + * @param group The name of the group. + * @return Return 0 if no errors, otherwise return the error number. + */ +int aim_ssi_delgroup(OscarData *od, const char *group) +{ + struct aim_ssi_item *del; + aim_tlv_t *tlv; - /* Modify the parent group */ - aim_ssi_itemlist_rebuildgroup(od->ssi.local, NULL); + if (!od) + return -EINVAL; + + /* Find the group */ + if (!(del = aim_ssi_itemlist_finditem(od->ssi.local, group, NULL, AIM_SSI_TYPE_GROUP))) + return -EINVAL; - /* Check if we should delete the parent's parent (the master group) */ - if ((del = aim_ssi_itemlist_find(od->ssi.local, 0x0000, 0x0000)) && (!del->data)) { - aim_ssi_itemlist_del(&od->ssi.local, del); - } - } + /* Don't delete the group if it's not empty */ + tlv = aim_tlv_gettlv(del->data, 0x00c8, 1); + if (tlv && tlv->length > 0) + return -EINVAL; + + /* Remove the item from the list */ + aim_ssi_itemlist_del(&od->ssi.local, del); + + /* Modify the parent group */ + aim_ssi_itemlist_rebuildgroup(od->ssi.local, group); /* Sync our local list with the server list */ return aim_ssi_sync(od);
--- a/libpurple/protocols/oscar/flap_connection.c Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/oscar/flap_connection.c Sun Aug 12 20:48:34 2007 +0000 @@ -129,8 +129,8 @@ new_current = rateclass_get_new_current(conn, rateclass, &now); + /* (Add 100ms padding to account for inaccuracies in the calculation) */ if (new_current < rateclass->alert + 100) - /* (Add 100ms padding to account for inaccuracies in the calculation) */ /* Not ready to send this SNAC yet--keep waiting. */ return TRUE; @@ -186,9 +186,9 @@ gettimeofday(&now, NULL); new_current = rateclass_get_new_current(conn, rateclass, &now); + /* (Add 100ms padding to account for inaccuracies in the calculation) */ if (new_current < rateclass->alert + 100) { - /* (Add 100ms padding to account for inaccuracies in the calculation) */ enqueue = TRUE; } else
--- a/libpurple/protocols/oscar/libaim.c Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/oscar/libaim.c Sun Aug 12 20:48:34 2007 +0000 @@ -78,7 +78,7 @@ oscar_convo_closed, /* convo_closed */ oscar_normalize, /* normalize */ oscar_set_icon, /* set_buddy_icon */ - NULL, /* remove_group */ + oscar_remove_group, /* remove_group */ NULL, /* get_cb_real_name */ NULL, /* set_chat_topic */ NULL, /* find_blist_chat */
--- a/libpurple/protocols/oscar/libicq.c Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/oscar/libicq.c Sun Aug 12 20:48:34 2007 +0000 @@ -78,7 +78,7 @@ oscar_convo_closed, /* convo_closed */ oscar_normalize, /* normalize */ oscar_set_icon, /* set_buddy_icon */ - NULL, /* remove_group */ + oscar_remove_group, /* remove_group */ NULL, /* get_cb_real_name */ NULL, /* set_chat_topic */ NULL, /* find_blist_chat */
--- a/libpurple/protocols/oscar/oscar.c Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/oscar/oscar.c Sun Aug 12 20:48:34 2007 +0000 @@ -161,7 +161,6 @@ static int purple_conv_chat_info_update (OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_conv_chat_incoming_msg(OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_email_parseupdate(OscarData *, FlapConnection *, FlapFrame *, ...); -static int purple_icon_error (OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_icon_parseicon (OscarData *, FlapConnection *, FlapFrame *, ...); static int oscar_icon_req (OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_parse_msgack (OscarData *, FlapConnection *, FlapFrame *, ...); @@ -195,7 +194,7 @@ static int purple_ssi_authreply (OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_ssi_gotadded (OscarData *, FlapConnection *, FlapFrame *, ...); -static gboolean purple_icon_timerfunc(gpointer data); +static void purple_icons_fetch(PurpleConnection *gc); static void recent_buddies_cb(const char *name, PurplePrefType type, gconstpointer value, gpointer data); void oscar_set_info(PurpleConnection *gc, const char *info); @@ -483,7 +482,7 @@ /* Attempt to send as ASCII */ if (oscar_charset_check(from) == AIM_CHARSET_ASCII) { - *msg = g_convert(from, strlen(from), "ASCII", "UTF-8", NULL, &msglen, NULL); + *msg = g_convert(from, -1, "ASCII", "UTF-8", NULL, &msglen, NULL); *charset = AIM_CHARSET_ASCII; *charsubset = 0x0000; *msglen_int = msglen; @@ -505,7 +504,7 @@ b = purple_find_buddy(account, destsn); if ((b != NULL) && (PURPLE_BUDDY_IS_ONLINE(b))) { - *msg = g_convert(from, strlen(from), "UCS-2BE", "UTF-8", NULL, &msglen, NULL); + *msg = g_convert(from, -1, "UCS-2BE", "UTF-8", NULL, &msglen, NULL); if (*msg != NULL) { *charset = AIM_CHARSET_UNICODE; @@ -528,7 +527,7 @@ * XXX - We need a way to only attempt to convert if we KNOW "from" * can be converted to "charsetstr" */ - *msg = g_convert(from, strlen(from), charsetstr, "UTF-8", NULL, &msglen, NULL); + *msg = g_convert(from, -1, charsetstr, "UTF-8", NULL, &msglen, NULL); if (*msg != NULL) { *charset = AIM_CHARSET_CUSTOM; *charsubset = 0x0000; @@ -539,7 +538,7 @@ /* * Nothing else worked, so send as UCS-2BE. */ - *msg = g_convert(from, strlen(from), "UCS-2BE", "UTF-8", NULL, &msglen, &err); + *msg = g_convert(from, -1, "UCS-2BE", "UTF-8", NULL, &msglen, &err); if (*msg != NULL) { *charset = AIM_CHARSET_UNICODE; *charsubset = 0x0000; @@ -1156,8 +1155,7 @@ od->iconconnecting = FALSE; - if (od->icontimer == 0) - od->icontimer = purple_timeout_add(100, purple_icon_timerfunc, gc); + purple_icons_fetch(gc); } static int @@ -1203,7 +1201,6 @@ oscar_data_addhandler(od, SNAC_FAMILY_AUTH, 0x0003, purple_parse_auth_resp, 0); oscar_data_addhandler(od, SNAC_FAMILY_AUTH, 0x0007, purple_parse_login, 0); oscar_data_addhandler(od, SNAC_FAMILY_AUTH, SNAC_SUBTYPE_AUTH_SECURID_REQUEST, purple_parse_auth_securid_request, 0); - oscar_data_addhandler(od, SNAC_FAMILY_BART, SNAC_SUBTYPE_BART_ERROR, purple_icon_error, 0); oscar_data_addhandler(od, SNAC_FAMILY_BART, SNAC_SUBTYPE_BART_RESPONSE, purple_icon_parseicon, 0); oscar_data_addhandler(od, SNAC_FAMILY_BOS, 0x0001, purple_parse_genericerr, 0); oscar_data_addhandler(od, SNAC_FAMILY_BOS, 0x0003, purple_bosrights, 0); @@ -1873,13 +1870,12 @@ saved_b16 = purple_buddy_icons_get_checksum_for_user(b); if (!b16 || !saved_b16 || strcmp(b16, saved_b16)) { - GSList *cur = od->requesticon; - while (cur && aim_sncmp((char *)cur->data, info->sn)) - cur = cur->next; - if (!cur) { - od->requesticon = g_slist_append(od->requesticon, g_strdup(purple_normalize(account, info->sn))); - if (od->icontimer == 0) - od->icontimer = purple_timeout_add(500, purple_icon_timerfunc, gc); + if (g_slist_find_custom(od->requesticon, info->sn, + (GCompareFunc)aim_sncmp) == NULL) + { + od->requesticon = g_slist_prepend(od->requesticon, + g_strdup(purple_normalize(account, info->sn))); + purple_icons_fetch(gc); } } g_free(b16); @@ -2265,8 +2261,9 @@ /* When other people ask you for authorization */ static void -purple_auth_grant(struct name_data *data) +purple_auth_grant(gpointer cbdata) { + struct name_data *data = cbdata; PurpleConnection *gc = data->gc; OscarData *od = gc->proto_data; @@ -2286,8 +2283,9 @@ } static void -purple_auth_dontgrant_msgprompt(struct name_data *data) +purple_auth_dontgrant_msgprompt(gpointer cbdata) { + struct name_data *data = cbdata; purple_request_input(data->gc, NULL, _("Authorization Denied Message:"), NULL, _("No reason given."), TRUE, FALSE, NULL, _("_OK"), G_CALLBACK(purple_auth_dontgrant), @@ -2408,8 +2406,8 @@ purple_account_request_authorization(account, sn, NULL, NULL, reason, purple_find_buddy(account, sn) != NULL, - G_CALLBACK(purple_auth_grant), - G_CALLBACK(purple_auth_dontgrant_msgprompt), data); + purple_auth_grant, + purple_auth_dontgrant_msgprompt, data); g_free(reason); } } break; @@ -3237,24 +3235,8 @@ return 1; } -static int purple_icon_error(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...) { - PurpleConnection *gc = od->gc; - char *sn; - - sn = od->requesticon->data; - purple_debug_misc("oscar", "removing %s from hash table\n", sn); - od->requesticon = g_slist_remove(od->requesticon, sn); - g_free(sn); - - if (od->icontimer == 0) - od->icontimer = purple_timeout_add(500, purple_icon_timerfunc, gc); - - return 1; -} - static int purple_icon_parseicon(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...) { PurpleConnection *gc = od->gc; - GSList *cur; va_list ap; char *sn; guint8 iconcsumtype, *iconcsum, *icon; @@ -3280,38 +3262,23 @@ g_free(b16); } - cur = od->requesticon; - while (cur) { - char *cursn = cur->data; - if (!aim_sncmp(cursn, sn)) { - od->requesticon = g_slist_remove(od->requesticon, cursn); - g_free(cursn); - cur = od->requesticon; - } else - cur = cur->next; - } - - if (od->icontimer == 0) - od->icontimer = purple_timeout_add(250, purple_icon_timerfunc, gc); - return 1; } -static gboolean purple_icon_timerfunc(gpointer data) { - PurpleConnection *gc = data; +static void +purple_icons_fetch(PurpleConnection *gc) +{ OscarData *od = gc->proto_data; aim_userinfo_t *userinfo; FlapConnection *conn; - od->icontimer = 0; - conn = flap_connection_getbytype(od, SNAC_FAMILY_BART); if (!conn) { if (!od->iconconnecting) { aim_srv_requestnew(od, SNAC_FAMILY_BART); od->iconconnecting = TRUE; } - return FALSE; + return; } if (od->set_icon) { @@ -3322,32 +3289,24 @@ } else { purple_debug_info("oscar", "Uploading icon to icon server\n"); - aim_bart_upload(od, purple_imgstore_get_data(img), + aim_bart_upload(od, purple_imgstore_get_data(img), purple_imgstore_get_size(img)); purple_imgstore_unref(img); } od->set_icon = FALSE; } - if (!od->requesticon) { - purple_debug_misc("oscar", - "no more icons to request\n"); - return FALSE; - } - - userinfo = aim_locate_finduserinfo(od, (char *)od->requesticon->data); - if ((userinfo != NULL) && (userinfo->iconcsumlen > 0)) { - aim_bart_request(od, od->requesticon->data, userinfo->iconcsumtype, userinfo->iconcsum, userinfo->iconcsumlen); - return FALSE; - } else { - gchar *sn = od->requesticon->data; - od->requesticon = g_slist_remove(od->requesticon, sn); - g_free(sn); - } - - od->icontimer = purple_timeout_add(100, purple_icon_timerfunc, gc); - - return FALSE; + while (od->requesticon != NULL) + { + userinfo = aim_locate_finduserinfo(od, (char *)od->requesticon->data); + if ((userinfo != NULL) && (userinfo->iconcsumlen > 0)) + aim_bart_request(od, od->requesticon->data, userinfo->iconcsumtype, userinfo->iconcsum, userinfo->iconcsumlen); + + g_free(od->requesticon->data); + od->requesticon = g_slist_delete_link(od->requesticon, od->requesticon); + } + + purple_debug_misc("oscar", "no more icons to request\n"); } /* @@ -4389,10 +4348,10 @@ charset = oscar_charset_check(str); if (charset == AIM_CHARSET_UNICODE) { - encoded = g_convert(str, strlen(str), "UCS-2BE", "UTF-8", NULL, ret_len, NULL); + encoded = g_convert(str, -1, "UCS-2BE", "UTF-8", NULL, ret_len, NULL); *encoding = "unicode-2-0"; } else if (charset == AIM_CHARSET_CUSTOM) { - encoded = g_convert(str, strlen(str), "ISO-8859-1", "UTF-8", NULL, ret_len, NULL); + encoded = g_convert(str, -1, "ISO-8859-1", "UTF-8", NULL, ret_len, NULL); *encoding = "iso-8859-1"; } else { encoded = g_strdup(str); @@ -4523,9 +4482,9 @@ { status_text = purple_markup_strip_html(status_html); /* If the status_text is longer than 60 character then truncate it */ - if (strlen(status_text) > 60) + if (strlen(status_text) > MAXAVAILMSGLEN) { - char *tmp = g_utf8_find_prev_char(status_text, &status_text[58]); + char *tmp = g_utf8_find_prev_char(status_text, &status_text[MAXAVAILMSGLEN - 2]); strcpy(tmp, "..."); } } @@ -4709,6 +4668,11 @@ } } +void oscar_remove_group(PurpleConnection *gc, PurpleGroup *group) +{ + aim_ssi_delgroup(gc->proto_data, group->name); +} + static gboolean purple_ssi_rerequestdata(gpointer data) { OscarData *od = data; @@ -4902,29 +4866,34 @@ /* Add from server list to local list */ for (curitem=od->ssi.local; curitem; curitem=curitem->next) { - if ((curitem->name == NULL) || (g_utf8_validate(curitem->name, -1, NULL))) + if ((curitem->name == NULL) || (g_utf8_validate(curitem->name, -1, NULL))) switch (curitem->type) { case 0x0000: { /* Buddy */ if (curitem->name) { - char *gname = aim_ssi_itemlist_findparentname(od->ssi.local, curitem->name); + struct aim_ssi_item *groupitem = aim_ssi_itemlist_find(od->ssi.local, curitem->gid, 0x0000); + char *gname = groupitem ? groupitem->name : NULL; char *gname_utf8 = gname ? oscar_utf8_try_convert(gc->account, gname) : NULL; char *alias = aim_ssi_getalias(od->ssi.local, gname, curitem->name); char *alias_utf8; + g = purple_find_group(gname_utf8 ? gname_utf8 : _("Orphans")); + if (g == NULL) { + g = purple_group_new(gname_utf8 ? gname_utf8 : _("Orphans")); + purple_blist_add_group(g, NULL); + } + if (alias != NULL) { if (g_utf8_validate(alias, -1, NULL)) alias_utf8 = g_strdup(alias); else alias_utf8 = oscar_utf8_try_convert(account, alias); + g_free(alias); } else alias_utf8 = NULL; - b = purple_find_buddy(gc->account, curitem->name); - /* Should gname be freed here? -- elb */ - /* Not with the current code, but that might be cleaner -- med */ - g_free(alias); + b = purple_find_buddy_in_group(gc->account, curitem->name, g); if (b) { /* Get server stored alias */ if (alias_utf8) { @@ -4934,13 +4903,8 @@ } else { b = purple_buddy_new(gc->account, curitem->name, alias_utf8); - if (!(g = purple_find_group(gname_utf8 ? gname_utf8 : _("Orphans")))) { - g = purple_group_new(gname_utf8 ? gname_utf8 : _("Orphans")); - purple_blist_add_group(g, NULL); - } - purple_debug_info("oscar", - "ssi: adding buddy %s to group %s to local list\n", curitem->name, gname_utf8 ? gname_utf8 : _("Orphans")); + "ssi: adding buddy %s to group %s to local list\n", curitem->name, g->name); purple_blist_add_buddy(b, NULL, g, NULL); } if (!aim_sncmp(curitem->name, account->username)) { @@ -4957,7 +4921,12 @@ } break; case 0x0001: { /* Group */ - /* Shouldn't add empty groups */ + char *gname = curitem->name; + char *gname_utf8 = gname ? oscar_utf8_try_convert(gc->account, gname) : NULL; + if (gname_utf8 != NULL && purple_find_group(gname_utf8) == NULL) { + g = purple_group_new(gname_utf8); + purple_blist_add_group(g, NULL); + } } break; case 0x0002: { /* Permit buddy */ @@ -5203,8 +5172,8 @@ purple_account_request_authorization(account, sn, NULL, (buddy ? purple_buddy_get_alias_only(buddy) : NULL), - reason, buddy != NULL, G_CALLBACK(purple_auth_grant), - G_CALLBACK(purple_auth_dontgrant_msgprompt), data); + reason, buddy != NULL, purple_auth_grant, + purple_auth_dontgrant_msgprompt, data); g_free(reason); return 1;
--- a/libpurple/protocols/oscar/oscar.h Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/oscar/oscar.h Sun Aug 12 20:48:34 2007 +0000 @@ -114,6 +114,11 @@ */ #define MAXCHATMSGLEN 512 +/* + * Found by trial and error. + */ +#define MAXAVAILMSGLEN 251 + /** * Maximum length for the password of an ICQ account */ @@ -444,7 +449,6 @@ GSList *requesticon; gboolean icq; - guint icontimer; guint getblisttimer; guint getinfotimer; @@ -1211,6 +1215,7 @@ int aim_ssi_addpermit(OscarData *od, const char *name); int aim_ssi_adddeny(OscarData *od, const char *name); int aim_ssi_delbuddy(OscarData *od, const char *name, const char *group); +int aim_ssi_delgroup(OscarData *od, const char *group); int aim_ssi_delpermit(OscarData *od, const char *name); int aim_ssi_deldeny(OscarData *od, const char *name); int aim_ssi_movebuddy(OscarData *od, const char *oldgn, const char *newgn, const char *sn);
--- a/libpurple/protocols/oscar/oscar_data.c Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/oscar/oscar_data.c Sun Aug 12 20:48:34 2007 +0000 @@ -95,8 +95,6 @@ g_free(od->email); g_free(od->newp); g_free(od->oldp); - if (od->icontimer > 0) - purple_timeout_remove(od->icontimer); if (od->getblisttimer > 0) purple_timeout_remove(od->getblisttimer); if (od->getinfotimer > 0)
--- a/libpurple/protocols/oscar/oscarcommon.h Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/oscar/oscarcommon.h Sun Aug 12 20:48:34 2007 +0000 @@ -82,6 +82,7 @@ void oscar_convo_closed(PurpleConnection *gc, const char *who); const char *oscar_normalize(const PurpleAccount *account, const char *str); void oscar_set_icon(PurpleConnection *gc, PurpleStoredImage *img); +void oscar_remove_group(PurpleConnection *gc, PurpleGroup *group); gboolean oscar_can_receive_file(PurpleConnection *gc, const char *who); void oscar_send_file(PurpleConnection *gc, const char *who, const char *file); PurpleXfer *oscar_new_xfer(PurpleConnection *gc, const char *who);
--- a/libpurple/protocols/qq/buddy_opt.c Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/qq/buddy_opt.c Sun Aug 12 20:48:34 2007 +0000 @@ -270,11 +270,11 @@ if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) { read_packet_b(data, &cursor, len, &reply); if (reply != QQ_ADD_BUDDY_AUTH_REPLY_OK) { - purple_debug(PURPLE_DEBUG_WARNING, "QQ", "Add buddy with auth request fails\n"); + purple_debug(PURPLE_DEBUG_WARNING, "QQ", "Add buddy with auth request failed\n"); if (NULL == (segments = split_data(data, len, "\x1f", 2))) return; msg_utf8 = qq_to_utf8(segments[1], QQ_CHARSET_DEFAULT); - purple_notify_error(gc, NULL, _("Add buddy with auth request fails"), msg_utf8); + purple_notify_error(gc, NULL, _("Add buddy with auth request failed"), msg_utf8); g_free(msg_utf8); } else { purple_debug(PURPLE_DEBUG_INFO, "QQ", "Add buddy with auth request OK\n"); @@ -305,6 +305,7 @@ purple_debug(PURPLE_DEBUG_WARNING, "QQ", "Remove buddy fails\n"); } else { /* if reply */ purple_debug(PURPLE_DEBUG_INFO, "QQ", "Remove buddy OK\n"); + /* TODO: We don't really need to notify the user about this, do we? */ purple_notify_info(gc, NULL, _("You have successfully removed a buddy"), NULL); } } else { @@ -333,7 +334,8 @@ purple_debug(PURPLE_DEBUG_WARNING, "QQ", "Remove self fails\n"); else { /* if reply */ purple_debug(PURPLE_DEBUG_INFO, "QQ", "Remove self from a buddy OK\n"); - purple_notify_info(gc, NULL, _("You have successfully removed yourself from a buddy"), NULL); + /* TODO: Does the user really need to be notified about this? */ + purple_notify_info(gc, NULL, _("You have successfully removed yourself from your friend's buddy list"), NULL); } } else { purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Error decrypt remove self reply\n"); @@ -413,7 +415,7 @@ g_free(nombre); } else { /* add OK */ qq_add_buddy_by_recv_packet(gc, for_uid, TRUE, TRUE); - msg = g_strdup_printf(_("You have added %d in buddy list"), for_uid); + msg = g_strdup_printf(_("You have added %d to buddy list"), for_uid); purple_notify_info(gc, NULL, msg, NULL); g_free(msg); }
--- a/libpurple/protocols/qq/group.c Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/qq/group.c Sun Aug 12 20:48:34 2007 +0000 @@ -117,7 +117,7 @@ purple_roomlist_set_in_progress(qd->roomlist, TRUE); purple_request_input(gc, _("QQ Qun"), - _("Please input external group ID"), + _("Please enter external group ID"), _("You can only search for permanent QQ groups\n"), NULL, FALSE, FALSE, NULL, _("Search"), G_CALLBACK(_qq_group_search_callback),
--- a/libpurple/protocols/qq/group_im.c Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/qq/group_im.c Sun Aug 12 20:48:34 2007 +0000 @@ -123,7 +123,7 @@ convert_as_pascal_string(*cursor, &reason_utf8, QQ_CHARSET_DEFAULT); - msg = g_strdup_printf(_("User %d applied to join group %d"), user_uid, external_group_id); + msg = g_strdup_printf(_("User %d requested to join group %d"), user_uid, external_group_id); reason = g_strdup_printf(_("Reason: %s"), reason_utf8); g = g_new0(group_member_opt, 1); @@ -177,7 +177,7 @@ convert_as_pascal_string(*cursor, &reason_utf8, QQ_CHARSET_DEFAULT); msg = g_strdup_printf - (_("You request to join group %d has been rejected by admin %d"), external_group_id, admin_uid); + (_("Your request to join group %d has been rejected by admin %d"), external_group_id, admin_uid); reason = g_strdup_printf(_("Reason: %s"), reason_utf8); purple_notify_warning(gc, _("QQ Qun Operation"), msg, reason); @@ -218,7 +218,7 @@ convert_as_pascal_string(*cursor, &reason_utf8, QQ_CHARSET_DEFAULT); msg = g_strdup_printf - (_("You request to join group %d has been approved by admin %d"), external_group_id, admin_uid); + (_("Your request to join group %d has been approved by admin %d"), external_group_id, admin_uid); purple_notify_warning(gc, _("QQ Qun Operation"), msg, NULL); @@ -254,7 +254,7 @@ g_return_if_fail(external_group_id > 0 && uid > 0); - msg = g_strdup_printf(_("You [%d] has exit group \"%d\""), uid, external_group_id); + msg = g_strdup_printf(_("You [%d] have left group \"%d\""), uid, external_group_id); purple_notify_info(gc, _("QQ Qun Operation"), msg, NULL); group = qq_group_find_by_id(gc, internal_group_id, QQ_INTERNAL_ID); @@ -288,7 +288,7 @@ g_return_if_fail(external_group_id > 0 && uid > 0); - msg = g_strdup_printf(_("You [%d] has been added by group \"%d\""), uid, external_group_id); + msg = g_strdup_printf(_("You [%d] have been added to group \"%d\""), uid, external_group_id); purple_notify_info(gc, _("QQ Qun Operation"), msg, _("This group has been added to your buddy list")); group = qq_group_find_by_id(gc, internal_group_id, QQ_INTERNAL_ID);
--- a/libpurple/protocols/qq/group_internal.c Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/qq/group_internal.c Sun Aug 12 20:48:34 2007 +0000 @@ -38,7 +38,7 @@ switch (group->my_status) { case QQ_GROUP_MEMBER_STATUS_NOT_MEMBER: - status_desc = _("I am not member"); + status_desc = _("I am not a member"); break; case QQ_GROUP_MEMBER_STATUS_IS_MEMBER: status_desc = _("I am a member");
--- a/libpurple/protocols/qq/group_join.c Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/qq/group_join.c Sun Aug 12 20:48:34 2007 +0000 @@ -230,7 +230,7 @@ purple_blist_remove_chat(chat); qq_group_delete_internal_record(qd, internal_group_id); } - purple_notify_info(gc, _("QQ Qun Operation"), _("You have successfully exited the group"), NULL); + purple_notify_info(gc, _("QQ Qun Operation"), _("You have successfully left the group"), NULL); } else { purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Invalid exit group reply, expect %d bytes, read %d bytes\n", expected_bytes, bytes); @@ -254,8 +254,8 @@ if (bytes == expected_bytes) purple_notify_info - (gc, _("QQ Group Auth"), - _("Your authorization operation has been accepted by the QQ server"), NULL); + (gc, _("QQ Group Auth"), + _("Your authorization request has been accepted by the QQ server"), NULL); else purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Invalid join group reply, expect %d bytes, read %d bytes\n", expected_bytes, bytes); @@ -325,8 +325,8 @@ errno = 0; external_group_id = strtol(external_group_id_ptr, NULL, 10); if (errno != 0) { - purple_notify_error(gc, _("Error"), - _("You inputted a group id outside the acceptable range"), NULL); + purple_notify_error(gc, _("Error"), + _("You entered a group ID outside the acceptable range"), NULL); return; } @@ -357,12 +357,12 @@ g->uid = internal_group_id; purple_request_action(gc, _("QQ Qun Operation"), - _("Are you sure to exit this Qun?"), + _("Are you sure you want to leave this Qun?"), _ ("Note, if you are the creator, \nthis operation will eventually remove this Qun."), 1, purple_connection_get_account(gc), NULL, NULL, g, 2, _("Cancel"), G_CALLBACK(qq_do_nothing_with_gc_and_uid), - _("Go ahead"), G_CALLBACK(_qq_group_exit_with_gc_and_id)); + _("Continue"), G_CALLBACK(_qq_group_exit_with_gc_and_id)); }
--- a/libpurple/protocols/qq/group_opt.c Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/qq/group_opt.c Sun Aug 12 20:48:34 2007 +0000 @@ -120,8 +120,8 @@ { g_return_if_fail(g != NULL && g->gc != NULL && g->member > 0); - qq_send_packet_get_info(g->gc, g->member, TRUE); /* we wanna see window */ - purple_request_action(g->gc, NULL, _("Do you wanna approve the request?"), "", 2, + qq_send_packet_get_info(g->gc, g->member, TRUE); /* we want to see window */ + purple_request_action(g->gc, NULL, _("Do you want to approve the request?"), "", 2, purple_connection_get_account(g->gc), NULL, NULL, g, 2, _("Reject"), G_CALLBACK(qq_group_reject_application_with_struct), @@ -134,7 +134,7 @@ g_return_if_fail(g != NULL && g->gc != NULL && g->member > 0); msg1 = g_strdup_printf(_("You rejected %d's request"), g->member); - msg2 = g_strdup(_("Input your reason:")); + msg2 = g_strdup(_("Enter your reason:")); nombre = uid_to_purple_name(g->member); purple_request_input(g->gc, /* title */ NULL, msg1, msg2, @@ -232,7 +232,7 @@ purple_debug(PURPLE_DEBUG_INFO, "QQ", "Succeed in modify members for Qun %d\n", group->external_group_id); - purple_notify_info(gc, _("QQ Qun Operation"), _("You have successfully modify Qun member"), NULL); + purple_notify_info(gc, _("QQ Qun Operation"), _("You have successfully modified Qun member"), NULL); } void qq_group_modify_info(PurpleConnection *gc, qq_group *group) @@ -302,7 +302,7 @@ purple_debug(PURPLE_DEBUG_INFO, "QQ", "Succeed in modify info for Qun %d\n", group->external_group_id); qq_group_refresh(gc, group); - purple_notify_info(gc, _("QQ Qun Operation"), _("You have successfully modify Qun information"), NULL); + purple_notify_info(gc, _("QQ Qun Operation"), _("You have successfully modified Qun information"), NULL); } /* we create a very simple group first, and then let the user to modify */
--- a/libpurple/protocols/qq/im.c Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/qq/im.c Sun Aug 12 20:48:34 2007 +0000 @@ -573,7 +573,7 @@ read_packet_b(data, &cursor, len, &reply); if (reply != QQ_SEND_IM_REPLY_OK) { purple_debug(PURPLE_DEBUG_WARNING, "QQ", "Send IM fail\n"); - purple_notify_error(gc, _("Server ACK"), _("Failed to send IM."), NULL); + purple_notify_error(gc, _("Error"), _("Failed to send IM."), NULL); } else purple_debug(PURPLE_DEBUG_INFO, "QQ", "IM ACK OK\n");
--- a/libpurple/protocols/qq/keep_alive.c Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/qq/keep_alive.c Sun Aug 12 20:48:34 2007 +0000 @@ -84,7 +84,7 @@ /* segments[0] and segment[1] are all 0x30 ("0") */ qd->all_online = strtol(segments[2], NULL, 10); if(0 == qd->all_online) - purple_connection_error(gc, _("Keep alive error, seems connection lost!")); + purple_connection_error(gc, _("Keep alive error")); g_free(qd->my_ip); qd->my_ip = g_strdup(segments[3]); qd->my_port = strtol(segments[4], NULL, 10);
--- a/libpurple/protocols/qq/login_logout.c Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/qq/login_logout.c Sun Aug 12 20:48:34 2007 +0000 @@ -405,7 +405,7 @@ ">>> %d bytes -> [default] decrypt and dump\n%s", buf_len, hex_dump); try_dump_as_gbk(buf, buf_len); - purple_connection_error(gc, _("Request login token error!")); + purple_connection_error(gc, _("Error requesting login token")); } g_free(hex_dump); }
--- a/libpurple/protocols/qq/qq.c Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/qq/qq.c Sun Aug 12 20:48:34 2007 +0000 @@ -195,7 +195,8 @@ { qq_buddy *q_bud; gchar *ip_str; - char *tmp, *tmp2; + char *tmp; + const char *tmp2; g_return_if_fail(b != NULL); @@ -206,11 +207,12 @@ { ip_str = gen_ip_str(q_bud->ip); if (strlen(ip_str) != 0) { - tmp = g_strdup_printf(_("%s Address"), - ((q_bud->comm_flag & QQ_COMM_FLAG_TCP_MODE) ? "TCP" : "UDP")); - tmp2 = g_strdup_printf("%s:%d", ip_str, q_bud->port); - purple_notify_user_info_add_pair(user_info, tmp, tmp2); - g_free(tmp2); + if (q_bud->comm_flag & QQ_COMM_FLAG_TCP_MODE) + tmp2 = _("TCP Address"); + else + tmp2 = _("UDP Address"); + tmp = g_strdup_printf("%s:%d", ip_str, q_bud->port); + purple_notify_user_info_add_pair(user_info, tmp2, tmp); g_free(tmp); } g_free(ip_str); @@ -238,7 +240,7 @@ if (q_bud->level) { tmp = g_strdup_printf("%d", q_bud->level); purple_notify_user_info_add_pair(user_info, _("Level"), tmp); - g_free(tmp); + g_free(tmp); } /* For debugging */ /* @@ -275,19 +277,19 @@ GList *types = NULL; status = purple_status_type_new_full(PURPLE_STATUS_AVAILABLE, - "available", _("QQ: Available"), FALSE, TRUE, FALSE); + "available", _("Available"), FALSE, TRUE, FALSE); types = g_list_append(types, status); status = purple_status_type_new_full(PURPLE_STATUS_AWAY, - "away", _("QQ: Away"), FALSE, TRUE, FALSE); + "away", _("Away"), FALSE, TRUE, FALSE); types = g_list_append(types, status); status = purple_status_type_new_full(PURPLE_STATUS_INVISIBLE, - "invisible", _("QQ: Invisible"), FALSE, TRUE, FALSE); + "invisible", _("Invisible"), FALSE, TRUE, FALSE); types = g_list_append(types, status); status = purple_status_type_new_full(PURPLE_STATUS_OFFLINE, - "offline", _("QQ: Offline"), FALSE, TRUE, FALSE); + "offline", _("Offline"), FALSE, TRUE, FALSE); types = g_list_append(types, status); status = purple_status_type_new_full(PURPLE_STATUS_MOBILE, @@ -416,7 +418,7 @@ g->uid = uid; purple_request_action(gc, _("Block Buddy"), - _("Are you sure to block this buddy?"), NULL, + _("Are you sure you want to block this buddy?"), NULL, 1, g, 2, _("Cancel"), G_CALLBACK(qq_do_nothing_with_gc_and_uid), @@ -470,7 +472,7 @@ PurpleConnection *gc = (PurpleConnection *) action->context; purple_request_input(gc, _("Create QQ Qun"), _("Input Qun name here"), - _("Only QQ member can create permanent Qun"), + _("Only QQ members can create permanent Qun"), "OpenQ", FALSE, FALSE, NULL, _("Create"), G_CALLBACK(qq_group_create_with_name), _("Cancel"), NULL, gc); } @@ -528,7 +530,7 @@ PurplePluginAction *act; m = NULL; - act = purple_plugin_action_new(_("Modify My Information"), _qq_menu_modify_my_info); + act = purple_plugin_action_new(_("Set My Information"), _qq_menu_modify_my_info); m = g_list_append(m, act); act = purple_plugin_action_new(_("Change Password"), _qq_menu_change_password); @@ -555,7 +557,7 @@ PurpleMenuAction *act; m = NULL; - act = purple_menu_action_new(_("Exit this QQ Qun"), PURPLE_CALLBACK(_qq_menu_unsubscribe_group), NULL, NULL); + act = purple_menu_action_new(_("Leave this QQ Qun"), PURPLE_CALLBACK(_qq_menu_unsubscribe_group), NULL, NULL); m = g_list_append(m, act); /* TODO: enable this
--- a/libpurple/protocols/qq/qq_proxy.c Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/qq/qq_proxy.c Sun Aug 12 20:48:34 2007 +0000 @@ -493,13 +493,8 @@ errno = 0; ret = send(qd->fd, data, len, 0); } - if (ret == -1) { - purple_connection_error(qd->gc, _("Socket send error")); - return ret; - } else if (errno == ECONNREFUSED) { - purple_connection_error(qd->gc, _("Connection refused")); - return ret; - } + if (ret == -1) + purple_connection_error(qd->gc, strerror(errno)); return ret; }
--- a/libpurple/protocols/qq/sys_msg.c Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/qq/sys_msg.c Sun Aug 12 20:48:34 2007 +0000 @@ -80,12 +80,11 @@ uid = g->uid; g_return_if_fail(gc != 0 && uid != 0); - qq_send_packet_get_info(gc, uid, TRUE); /* we wanna see window */ + qq_send_packet_get_info(gc, uid, TRUE); /* we want to see window */ nombre = uid_to_purple_name(uid); - /* TODO: 'wanna' is not an appropriate word for this string. Fix after string freeze. */ purple_request_action - (gc, NULL, _("Do you wanna approve the request?"), "", 2, + (gc, NULL, _("Do you want to approve the request?"), "", 2, purple_connection_get_account(gc), nombre, NULL, g, 2, _("Reject"), G_CALLBACK(qq_reject_add_request_with_gc_and_uid), @@ -105,11 +104,10 @@ uid = g->uid; g_return_if_fail(gc != 0 && uid != 0); - qq_send_packet_get_info(gc, uid, TRUE); /* we wanna see window */ - /* TODO: 'wanna' is not an appropriate word for this string. Fix after string freeze. */ + qq_send_packet_get_info(gc, uid, TRUE); /* we want to see window */ nombre = uid_to_purple_name(uid); purple_request_action - (gc, NULL, _("Do you wanna add this buddy?"), "", 2, + (gc, NULL, _("Do you want to add this buddy?"), "", 2, purple_connection_get_account(gc), nombre, NULL, g, 2, _("Cancel"), NULL, @@ -175,7 +173,7 @@ _("Add"), G_CALLBACK(qq_add_buddy_with_gc_and_uid), _("Search"), G_CALLBACK(_qq_search_before_add_with_gc_and_uid)); } else { - message = g_strdup_printf(_("%s has added you [%s]"), from, to); + message = g_strdup_printf(_("%s has added you [%s] to his or her buddy list"), from, to); _qq_sys_msg_log_write(gc, message, from); purple_notify_info(gc, NULL, message, NULL); } @@ -211,7 +209,7 @@ qd = (qq_data *) gc->proto_data; qq_add_buddy_by_recv_packet(gc, strtol(from, NULL, 10), TRUE, TRUE); - message = g_strdup_printf(_("User %s has approved your request"), from); + message = g_strdup_printf(_("User %s approved your request"), from); _qq_sys_msg_log_write(gc, message, from); purple_notify_info(gc, NULL, message, NULL); @@ -236,9 +234,8 @@ name = uid_to_purple_name(uid); - /* TODO: 'wanna' is not an appropriate word for this string. Fix after string freeze */ /* TODO: this should go through purple_account_request_authorization() */ - message = g_strdup_printf(_("%s wanna add you [%s] as friends"), from, to); + message = g_strdup_printf(_("%s wants to add you [%s] as a friend"), from, to); reason = g_strdup_printf(_("Message: %s"), msg_utf8); _qq_sys_msg_log_write(gc, message, from);
--- a/libpurple/protocols/yahoo/Makefile.am Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/yahoo/Makefile.am Sun Aug 12 20:48:34 2007 +0000 @@ -9,6 +9,8 @@ yahoo.h \ yahoochat.h \ yahoochat.c \ + yahoo_aliases.c \ + yahoo_alisaes.h \ yahoo_auth.c \ yahoo_auth.h \ yahoo_crypt.h \
--- a/libpurple/protocols/yahoo/Makefile.mingw Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/yahoo/Makefile.mingw Sun Aug 12 20:48:34 2007 +0000 @@ -40,6 +40,7 @@ C_SRC = util.c \ yahoo.c \ yahoochat.c \ + yahoo_aliases.c \ yahoo_auth.c \ yahoo_crypt.c \ yahoo_doodle.c \
--- a/libpurple/protocols/yahoo/util.c Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/yahoo/util.c Sun Aug 12 20:48:34 2007 +0000 @@ -61,7 +61,7 @@ else to_codeset = purple_account_get_string(purple_connection_get_account(gc), "local_charset", "ISO-8859-1"); - ret = g_convert_with_fallback(str, strlen(str), to_codeset, "UTF-8", "?", NULL, NULL, NULL); + ret = g_convert_with_fallback(str, -1, to_codeset, "UTF-8", "?", NULL, NULL, NULL); if (ret) return ret; else @@ -92,7 +92,7 @@ else from_codeset = purple_account_get_string(purple_connection_get_account(gc), "local_charset", "ISO-8859-1"); - ret = g_convert_with_fallback(str, strlen(str), "UTF-8", from_codeset, NULL, NULL, NULL, NULL); + ret = g_convert_with_fallback(str, -1, "UTF-8", from_codeset, NULL, NULL, NULL, NULL); if (ret) return ret;
--- a/libpurple/protocols/yahoo/yahoo.c Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/yahoo/yahoo.c Sun Aug 12 20:48:34 2007 +0000 @@ -41,6 +41,7 @@ #include "yahoo.h" #include "yahoochat.h" +#include "yahoo_aliases.h" #include "yahoo_auth.h" #include "yahoo_crypt.h" #include "yahoo_doodle.h" @@ -196,6 +197,8 @@ GSList *l = pkt->hash; YahooFriend *f = NULL; char *name = NULL; + gboolean unicode = FALSE; + char *message = NULL; if (pkt->service == YAHOO_SERVICE_LOGOFF && pkt->status == -1) { gc->wants_to_die = TRUE; @@ -269,7 +272,7 @@ break; case 19: /* custom message */ if (f) - yahoo_friend_set_status_message(f, yahoo_string_decode(gc, pair->value, FALSE)); + message = pair->value; break; case 11: /* this is the buddy's session id */ break; @@ -380,6 +383,10 @@ g_free(tmp); } break; + case 97: /* Unicode status message */ + unicode = !strcmp(pair->value, "1"); + break; + default: purple_debug(PURPLE_DEBUG_ERROR, "yahoo", "Unknown status key %d\n", pair->key); @@ -389,6 +396,9 @@ l = l->next; } + if (message && f) + yahoo_friend_set_status_message(f, yahoo_string_decode(gc, message, unicode)); + if (name && f) /* update the last buddy */ yahoo_update_status(gc, name, f); } @@ -484,7 +494,8 @@ if (yd->cookie_t) g_free(yd->cookie_t); yd->cookie_t = _getcookie(c); - } + } else + purple_debug_info("yahoo", "Ignoring unrecognized cookie '%c'\n", c[0]); } static void yahoo_process_list_15(PurpleConnection *gc, struct yahoo_packet *pkt) @@ -508,7 +519,7 @@ l = l->next; switch (pair->key) { - case 302: + case 302: /* This is always 318 before a group, 319 before the first s/n in a group, 320 before any ignored s/n. * It is not sent for s/n's in a group after the first. * All ignored s/n's are listed last, so when we see a 320 we clear the group and begin marking the @@ -548,7 +559,7 @@ } else { /* This buddy is on the ignore list (and therefore in no group) */ - purple_privacy_deny_add(account, norm_bud, 1); + purple_privacy_deny_add(account, norm_bud, 1); } break; case 241: /* another protocol user */ @@ -702,6 +713,8 @@ yd->tmp_serv_plist = NULL; } + /* Now that we've got the list, request aliases */ + yahoo_fetch_aliases(gc); } static void yahoo_process_notify(PurpleConnection *gc, struct yahoo_packet *pkt) @@ -816,7 +829,7 @@ { g_hash_table_replace(yd->imvironments, g_strdup(im->from), g_strdup(imv)); - if (strcmp(imv, "doodle;11") == 0) + if (strstr(imv, "doodle;") != NULL) { PurpleWhiteboard *wb; @@ -825,6 +838,8 @@ return; } + /* I'm not sure the following ever happens -DAA */ + wb = purple_whiteboard_get_session(gc->account, im->from); /* If a Doodle session doesn't exist between this user */ @@ -868,17 +883,17 @@ PurpleAccount *account; PurpleConversation *c; char *username, *str; - + account = purple_connection_get_account(gc); c = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, im->from); - + if ((buddy = purple_find_buddy(account, im->from)) != NULL) username = g_markup_escape_text(purple_buddy_get_alias(buddy), -1); else username = g_markup_escape_text(im->from, -1); - + str = g_strdup_printf(_("%s just sent you a Buzz!"), username); - + purple_conversation_write(c, NULL, str, PURPLE_MESSAGE_SYSTEM|PURPLE_MESSAGE_NOTIFY, im->time); g_free(username); @@ -938,7 +953,8 @@ }; static void -yahoo_buddy_add_authorize_cb(struct yahoo_add_request *add_req) { +yahoo_buddy_add_authorize_cb(gpointer data) { + struct yahoo_add_request *add_req = data; g_free(add_req->id); g_free(add_req->who); g_free(add_req->msg); @@ -982,9 +998,10 @@ } static void -yahoo_buddy_add_deny_reason_cb(struct yahoo_add_request *add_req) { +yahoo_buddy_add_deny_reason_cb(gpointer data) { + struct yahoo_add_request *add_req = data; purple_request_input(add_req->gc, NULL, _("Authorization denied message:"), - NULL, _("No reason given."), TRUE, FALSE, NULL, + NULL, _("No reason given."), TRUE, FALSE, NULL, _("OK"), G_CALLBACK(yahoo_buddy_add_deny_cb), _("Cancel"), G_CALLBACK(yahoo_buddy_add_deny_noreason_cb), purple_connection_get_account(add_req->gc), add_req->who, NULL, @@ -1027,8 +1044,8 @@ */ purple_account_request_authorization(purple_connection_get_account(gc), add_req->who, add_req->id, NULL, add_req->msg, purple_find_buddy(purple_connection_get_account(gc),add_req->who) != NULL, - G_CALLBACK(yahoo_buddy_add_authorize_cb), - G_CALLBACK(yahoo_buddy_add_deny_reason_cb), + yahoo_buddy_add_authorize_cb, + yahoo_buddy_add_deny_reason_cb, add_req); } else { g_free(add_req->id); @@ -1152,10 +1169,10 @@ { PurpleAccount *account = purple_connection_get_account(gc); struct yahoo_data *yd = gc->proto_data; - char *who = NULL; - char *email = NULL; - char *subj = NULL; - char *yahoo_mail_url = (yd->jp? YAHOOJP_MAIL_URL: YAHOO_MAIL_URL); + const char *who = NULL; + const char *email = NULL; + const char *subj = NULL; + const char *yahoo_mail_url = (yd->jp? YAHOOJP_MAIL_URL: YAHOO_MAIL_URL); int count = 0; GSList *l = pkt->hash; @@ -3242,11 +3259,79 @@ purple_connection_set_display_name(gc, entry); } +static void +yahoo_get_inbox_token_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, + const gchar *token, size_t len, const gchar *error_message) +{ + PurpleConnection *gc = user_data; + gboolean set_cookie = FALSE; + char *url; + + g_return_if_fail(PURPLE_CONNECTION_IS_VALID(gc)); + + if (error_message != NULL) + purple_debug_error("yahoo", "Requesting mail login token failed: %s\n", error_message); + else if (len > 0 && token && *token) { + /* Should we not be hardcoding the rd url? */ + url = g_strdup_printf( + "http://login.yahoo.com/config/reset_cookies_token?" + ".token=%s" + "&.done=http://us.rd.yahoo.com/messenger/client/%%3fhttp://mail.yahoo.com/", + token); + set_cookie = TRUE; + } + + if (!set_cookie) { + struct yahoo_data *yd = gc->proto_data; + purple_debug_error("yahoo", "No mail login token; forwarding to login screen."); + url = g_strdup(yd->jp ? YAHOOJP_MAIL_URL : YAHOO_MAIL_URL); + } + + /* Open the mailbox with the parsed url data */ + purple_notify_uri(gc, url); + + g_free(url); +} + + +static void yahoo_show_inbox(PurplePluginAction *action) +{ + /* Setup a cookie that can be used by the browser */ + /* XXX I have no idea how this will work with Yahoo! Japan. */ + + PurpleConnection *gc = action->context; + struct yahoo_data *yd = gc->proto_data; + + PurpleUtilFetchUrlData *url_data; + const char* base_url = "http://login.yahoo.com"; + char *request = g_strdup_printf( + "POST /config/cookie_token HTTP/1.0\r\n" + "Cookie: T=%s; path=/; domain=.yahoo.com; Y=%s;\r\n" + "User-Agent: Mozilla/4.0 (compatible; MSIE 5.5)\r\n" + "Host: login.yahoo.com\r\n" + "Content-Length: 0\r\n\r\n", + yd->cookie_t, yd->cookie_y); + + url_data = purple_util_fetch_url_request(base_url, FALSE, + "Mozilla/4.0 (compatible; MSIE 5.5)", TRUE, request, FALSE, + yahoo_get_inbox_token_cb, gc); + + g_free(request); + + if (url_data == NULL) { + const char *yahoo_mail_url = (yd->jp ? YAHOOJP_MAIL_URL : YAHOO_MAIL_URL); + purple_debug_error("yahoo", + "Unable to request mail login token; forwarding to login screen."); + purple_notify_uri(gc, yahoo_mail_url); + } + +} + + static void yahoo_show_act_id(PurplePluginAction *action) { PurpleConnection *gc = (PurpleConnection *) action->context; - /* XXX Typo: This should be _("Activate which ID?") - fix after string freeze is over */ - purple_request_input(gc, NULL, _("Active which ID?"), NULL, + purple_request_input(gc, NULL, _("Activate which ID?"), NULL, purple_connection_get_display_name(gc), FALSE, FALSE, NULL, _("OK"), G_CALLBACK(yahoo_act_id), _("Cancel"), NULL, @@ -3277,6 +3362,11 @@ yahoo_show_chat_goto); m = g_list_append(m, act); + m = g_list_append(m, NULL); + act = purple_plugin_action_new(_("Open Inbox"), + yahoo_show_inbox); + m = g_list_append(m, act); + return m; } @@ -3315,7 +3405,7 @@ */ wb = purple_whiteboard_get_session(gc->account, who); if (wb) - yahoo_packet_hash_str(pkt, 63, "doodle;11"); + yahoo_packet_hash_str(pkt, 63, DOODLE_IMV_KEY); else { const char *imv; @@ -3377,6 +3467,7 @@ const char *msg = NULL; char *tmp = NULL; char *conv_msg = NULL; + gboolean utf8 = TRUE; if (!purple_status_is_active(status)) return; @@ -3393,13 +3484,13 @@ msg = purple_status_get_attr_string(status, "message"); if (purple_status_is_available(status)) { - tmp = yahoo_string_encode(gc, msg, NULL); + tmp = yahoo_string_encode(gc, msg, &utf8); conv_msg = purple_markup_strip_html(tmp); g_free(tmp); } else { if ((msg == NULL) || (*msg == '\0')) msg = _("Away"); - tmp = yahoo_string_encode(gc, msg, NULL); + tmp = yahoo_string_encode(gc, msg, &utf8); conv_msg = purple_markup_strip_html(tmp); g_free(tmp); } @@ -3417,6 +3508,7 @@ yahoo_packet_hash_int(pkt, 10, yd->current_status); if (yd->current_status == YAHOO_STATUS_CUSTOM) { + yahoo_packet_hash_str(pkt, 97, utf8 ? "1" : 0); yahoo_packet_hash_str(pkt, 19, conv_msg); } else { yahoo_packet_hash_str(pkt, 19, ""); @@ -3591,8 +3683,18 @@ group2 = yahoo_string_encode(gc, group, NULL); pkt = yahoo_packet_new(YAHOO_SERVICE_ADDBUDDY, YAHOO_STATUS_AVAILABLE, 0); - yahoo_packet_hash(pkt, "ssss", 1, purple_connection_get_display_name(gc), - 7, buddy->name, 65, group2, 14, ""); + yahoo_packet_hash(pkt, "ssssssssss", + 14, "", + 65, group2, + 97, "1", + 1, purple_connection_get_display_name(gc), + 302, "319", + 300, "319", + 7, buddy->name, + 334, "0", + 301, "319", + 303, "319" + ); yahoo_packet_send_and_free(pkt, yd); g_free(group2); } @@ -3729,16 +3831,12 @@ return; } - /* Step 1: Add buddy to new group. */ - pkt = yahoo_packet_new(YAHOO_SERVICE_ADDBUDDY, YAHOO_STATUS_AVAILABLE, 0); - yahoo_packet_hash(pkt, "ssss", 1, purple_connection_get_display_name(gc), - 7, who, 65, gpn, 14, ""); + pkt = yahoo_packet_new(YAHOO_SERVICE_CHGRP_15, YAHOO_STATUS_AVAILABLE, 0); + yahoo_packet_hash(pkt, "ssssssss", 1, purple_connection_get_display_name(gc), + 302, "240", 300, "240", 7, who, 224, gpo, 264, gpn, 301, + "240", 303, "240"); yahoo_packet_send_and_free(pkt, yd); - /* Step 2: Remove buddy from old group */ - pkt = yahoo_packet_new(YAHOO_SERVICE_REMBUDDY, YAHOO_STATUS_AVAILABLE, 0); - yahoo_packet_hash(pkt, "sss", 1, purple_connection_get_display_name(gc), 7, who, 65, gpo); - yahoo_packet_send_and_free(pkt, yd); g_free(gpn); g_free(gpo); } @@ -4017,7 +4115,7 @@ NULL, /* register_user */ NULL, /* get_cb_info */ NULL, /* get_cb_away */ - NULL, /* alias_buddy */ + yahoo_update_alias, /* alias_buddy */ yahoo_change_buddys_group, yahoo_rename_group, NULL, /* buddy_free */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/yahoo/yahoo_aliases.c Sun Aug 12 20:48:34 2007 +0000 @@ -0,0 +1,245 @@ +/* + * purple + * + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + + +#include "internal.h" + +#include "account.h" +#include "accountopt.h" +#include "blist.h" +#include "debug.h" +#include "util.h" +#include "version.h" +#include "yahoo.h" +#include "yahoo_aliases.h" +#include "yahoo_packet.h" + +/* I hate hardcoding this stuff, but Yahoo never sends us anything to use. Someone in the know may be able to tweak this URL */ +#define YAHOO_ALIAS_FETCH_URL "http://address.yahoo.com/yab/us?v=XM&prog=ymsgr&.intl=us&diffs=1&t=0&tags=short&rt=0&prog-ver=8.1.0.249&useutf8=1&legenc=codepage-1252" +#define YAHOO_ALIAS_UPDATE_URL "http://address.yahoo.com/yab/us?v=XM&prog=ymsgr&.intl=us&sync=1&tags=short&noclear=1&useutf8=1&legenc=codepage-1252" + +void yahoo_update_alias(PurpleConnection *gc, const char *who, const char *alias); + +/** + * Stuff we want passed to the callback function + */ +struct callback_data { + PurpleConnection *gc; + const char *id; +}; + + +/************************************************************************** + * Alias Fetch Functions + **************************************************************************/ + +static void +yahoo_fetch_aliases_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data,const gchar *url_text, size_t len, const gchar *error_message) +{ + if (len == 0) { + purple_debug_info("yahoo","No Aliases to process\n"); + } else { + const char *yid, *full_name, *nick_name, *alias, *id, *fn, *ln, *nn; + struct callback_data *cb = user_data; + PurpleBuddy *b = NULL; + xmlnode *item, *contacts; + + /* Put our web response into a xmlnode for easy management */ + contacts = xmlnode_from_str(url_text, -1); + + if (contacts == NULL) { + purple_debug_error("yahoo_aliases","Badly formed XML\n"); + return; + } + purple_debug_info("yahoo", "Fetched %i bytes of alias data\n", len); + + /* Loop around and around and around until we have gone through all the received aliases */ + for(item = xmlnode_get_child(contacts, "ct"); item; item = xmlnode_get_next_twin(item)) { + /* Yahoo replies with two types of contact (ct) record, we are only interested in the alias ones */ + if ((yid = xmlnode_get_attrib(item, "yi"))) { + /* Grab all the bits of information we can */ + fn = xmlnode_get_attrib(item,"fn"); + ln = xmlnode_get_attrib(item,"ln"); + nn = xmlnode_get_attrib(item,"nn"); + id = xmlnode_get_attrib(item,"id"); + + /* Yahoo stores first and last names separately, lets put them together into a full name */ + full_name = g_strstrip(g_strdup_printf("%s %s", (fn != NULL ? fn : "") , (ln != NULL ? ln : ""))); + nick_name = (nn != NULL ? g_strstrip(g_strdup_printf("%s", nn)) : NULL); + + if (nick_name != NULL) + alias = nick_name; /* If we have a nickname from Yahoo, let's use it */ + else if (strlen(full_name) != 0) + alias = full_name; /* If no Yahoo nickname, we can use the full_name created above */ + else + alias = NULL; /* No nickname, first name or last name, then you get no alias !! */ + + /* Find the local buddy that matches */ + b = purple_find_buddy(cb->gc->account, yid); + + /* If we don't find a matching buddy, ignore the alias !! */ + if (b != NULL) { + /* Create an object that we can attach to the buddies proto_data pointer */ + struct YahooUser *yu; + yu = g_new0(struct YahooUser, 1); + yu->id = g_strdup(id); + yu->firstname = g_strdup(fn); + yu->lastname = g_strdup(ln); + yu->nickname = g_strdup(nn); + b->proto_data=yu; + + /* Finally, if we received an alias, we better update the buddy list */ + if (alias != NULL) { + serv_got_alias(cb->gc, yid, alias); + purple_debug_info("yahoo","Fetched alias '%s' (%s)\n",alias,id); + } else if (g_strcasecmp((alias!=NULL?alias:""),(b->alias!=NULL?b->alias:"")) != 0) { + /* Or if we have an alias that Yahoo doesn't, send it up */ + yahoo_update_alias(cb->gc, yid, b->alias); + purple_debug_info("yahoo","Sent alias '%s'\n", b->alias); + } + } else { + purple_debug_info("yahoo", "Bizarre, received alias for %s, but they are not on your list...\n", yid); + } + } + } + xmlnode_free(contacts); + g_free(cb); + } +} + +void +yahoo_fetch_aliases(PurpleConnection *gc) +{ + struct yahoo_data *yd = gc->proto_data; + struct callback_data *cb; + char *url, *request, *webpage, *webaddress, *strtmp; + int inttmp; + + /* Using callback_data so I have access to gc in the callback function */ + cb = g_new0(struct callback_data, 1); + cb->gc = gc; + + /* Build all the info to make the web request */ + url = g_strdup(YAHOO_ALIAS_FETCH_URL); + purple_url_parse(url, &webaddress, &inttmp, &webpage, &strtmp, &strtmp); + request = g_strdup_printf("GET /%s HTTP/1.1\r\n" + "User-Agent: Mozilla/4.0 (compatible; MSIE 5.5)\r\n" + "Cookie: T=%s; Y=%s\r\n" + "Host: %s\r\n" + "Cache-Control: no-cache\r\n\r\n", + webpage, yd->cookie_t,yd->cookie_y, webaddress); + + /* We have a URL and some header information, let's connect and get some aliases */ + purple_util_fetch_url_request(url, FALSE, NULL, TRUE, request, FALSE, yahoo_fetch_aliases_cb, cb); + + g_free(url); + g_free(request); +} + +/************************************************************************** + * Alias Update Functions + **************************************************************************/ + +static void +yahoo_update_alias_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data,const gchar *url_text, size_t len, const gchar *error_message) +{ + xmlnode *node, *result; + struct callback_data *cb = user_data; + + result = xmlnode_from_str(url_text, -1); + + purple_debug_info("yahoo", "ID: %s, Return data: %s\n",cb->id, url_text); + + if (result == NULL) { + purple_debug_error("yahoo","Alias update faild: Badly formed response\n"); + return; + } + + if ((node = xmlnode_get_child(result, "ct"))) { + if (g_ascii_strncasecmp(xmlnode_get_attrib(node, "id"), cb->id, strlen(cb->id))==0) + purple_debug_info("yahoo", "Alias update succeeded\n"); + else + purple_debug_error("yahoo", "Alias update failed (Contact record return mismatch)\n"); + } else { + purple_debug_info("yahoo", "Alias update failed (No contact record returned)\n"); + } + + g_free(cb); + xmlnode_free(result); +} + +void +yahoo_update_alias(PurpleConnection *gc, const char *who, const char *alias) +{ + struct yahoo_data *yd; + struct YahooUser *yu; + char *content, *url, *request, *webpage, *webaddress, *strtmp; + int inttmp; + struct callback_data *cb; + PurpleBuddy *buddy; + + g_return_if_fail(alias!= NULL); + g_return_if_fail(who!=NULL); + g_return_if_fail(gc!=NULL); + + purple_debug_info("yahoo", "Sending '%s' as new alias for user '%s'.\n",alias, who); + + buddy = purple_find_buddy(gc->account, who); + if (buddy->proto_data == NULL) { + purple_debug_info("yahoo", "Missing proto_data (get_yahoo_aliases must have failed), bailing out\n"); + return; + } + + yd = gc->proto_data; + yu = buddy->proto_data; + + /* Using callback_data so I have access to gc in the callback function */ + cb = g_new0(struct callback_data, 1); + cb->id = g_strdup(yu->id); + + /* Build all the info to make the web request */ + url = g_strdup(YAHOO_ALIAS_UPDATE_URL); + purple_url_parse(url, &webaddress, &inttmp, &webpage, &strtmp, &strtmp); + + content = g_strdup_printf("<?xml version=\"1.0\" encoding=\"utf-8\"?><ab k=\"%s\" cc=\"1\">\n" + "<ct e=\"1\" yi='%s' id='%s' nn='%s' pr='0' />\n</ab>\r\n", + gc->account->username, who, yu->id, g_markup_escape_text(alias, strlen(alias))); + + request = g_strdup_printf("POST /%s HTTP/1.1\r\n" + "User-Agent: Mozilla/4.0 (compatible; MSIE 5.5)\r\n" + "Cookie: T=%s; Y=%s\r\n" + "Host: %s\r\n" + "Content-Length: %" G_GSIZE_FORMAT "\r\n" + "Cache-Control: no-cache\r\n\r\n" + "%s", + webpage, yd->cookie_t,yd->cookie_y, webaddress, + strlen(content), content); + + /* We have a URL and some header information, let's connect and update the alias */ + purple_util_fetch_url_request(url, FALSE, NULL, TRUE, request, FALSE, yahoo_update_alias_cb, cb); + + g_free(content); + g_free(url); + g_free(request); +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/yahoo/yahoo_aliases.h Sun Aug 12 20:48:34 2007 +0000 @@ -0,0 +1,50 @@ +/* + * purple + * + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + + +#include "internal.h" + +#include "account.h" +#include "accountopt.h" +#include "blist.h" +#include "debug.h" +#include "util.h" +#include "version.h" +#include "yahoo.h" +#include "yahoo_packet.h" + + +/** + * The additional protocol specific info attached to each buddy. We need + * to store the unique numeric id number to allow us to push alias changes. + */ +struct YahooUser +{ + const char *id; /* The yahoo accountid for this buddy (not YahooID but numeric value) */ + char *firstname; /* Storing this information for no real reason, just because */ + char *lastname; /* Storing this information for no real reason, just because */ + char *nickname; /* Storing this information for no real reason, just because */ +}; + +void yahoo_update_alias(PurpleConnection *gc, const char *who, const char *alias); +void yahoo_fetch_aliases(PurpleConnection *gc);
--- a/libpurple/protocols/yahoo/yahoo_doodle.c Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/yahoo/yahoo_doodle.c Sun Aug 12 20:48:34 2007 +0000 @@ -125,47 +125,12 @@ * sessions */ + yahoo_doodle_command_send_ready(gc, to); yahoo_doodle_command_send_request(gc, to); - yahoo_doodle_command_send_ready(gc, to); } -void yahoo_doodle_process(PurpleConnection *gc, const char *me, const char *from, - const char *command, const char *message) -{ - if(!command) - return; - - /* Now check to see what sort of Doodle message it is */ - switch(atoi(command)) - { - case DOODLE_CMD_REQUEST: - yahoo_doodle_command_got_request(gc, from); - break; - - case DOODLE_CMD_READY: - yahoo_doodle_command_got_ready(gc, from); - break; - - case DOODLE_CMD_CLEAR: - yahoo_doodle_command_got_clear(gc, from); - break; - - case DOODLE_CMD_DRAW: - yahoo_doodle_command_got_draw(gc, from, message); - break; - - case DOODLE_CMD_EXTRA: - yahoo_doodle_command_got_extra(gc, from, message); - break; - - case DOODLE_CMD_CONFIRM: - yahoo_doodle_command_got_confirm(gc, from); - break; - } -} - -void yahoo_doodle_command_got_request(PurpleConnection *gc, const char *from) +static void yahoo_doodle_command_got_request(PurpleConnection *gc, const char *from) { PurpleAccount *account; PurpleWhiteboard *wb; @@ -197,7 +162,7 @@ purple_whiteboard_create(account, from, DOODLE_STATE_REQUESTED); - yahoo_doodle_command_send_request(gc, from); + yahoo_doodle_command_send_ready(gc, from); } /* TODO Might be required to clear the canvas of an existing doodle @@ -205,12 +170,12 @@ */ } -void yahoo_doodle_command_got_ready(PurpleConnection *gc, const char *from) +static void yahoo_doodle_command_got_ready(PurpleConnection *gc, const char *from) { PurpleAccount *account; PurpleWhiteboard *wb; - purple_debug_info("yahoo", "doodle: Got Ready (%s)\n", from); + purple_debug_info("yahoo", "doodle: Got Ready(%s)\n", from); account = purple_connection_get_account(gc); @@ -230,8 +195,7 @@ yahoo_doodle_command_send_confirm(gc, from); } - - if(wb->state == DOODLE_STATE_ESTABLISHED) + else if(wb->state == DOODLE_STATE_ESTABLISHED) { /* TODO Ask whether to save picture too */ purple_whiteboard_clear(wb); @@ -239,16 +203,16 @@ /* NOTE Not sure about this... I am trying to handle if the remote user * already thinks we're in a session with them (when their chat message - * contains the doodle;11 imv key) + * contains the doodle imv key) */ - if(wb->state == DOODLE_STATE_REQUESTED) + else if(wb->state == DOODLE_STATE_REQUESTED) { /* purple_whiteboard_start(wb); */ - yahoo_doodle_command_send_request(gc, from); + yahoo_doodle_command_send_ready(gc, from); } } -void yahoo_doodle_command_got_draw(PurpleConnection *gc, const char *from, const char *message) +static void yahoo_doodle_command_got_draw(PurpleConnection *gc, const char *from, const char *message) { PurpleAccount *account; PurpleWhiteboard *wb; @@ -304,7 +268,8 @@ g_list_free(d_list); } -void yahoo_doodle_command_got_clear(PurpleConnection *gc, const char *from) + +static void yahoo_doodle_command_got_clear(PurpleConnection *gc, const char *from) { PurpleAccount *account; PurpleWhiteboard *wb; @@ -329,7 +294,8 @@ } } -void + +static void yahoo_doodle_command_got_extra(PurpleConnection *gc, const char *from, const char *message) { purple_debug_info("yahoo", "doodle: Got Extra (%s)\n", from); @@ -340,7 +306,7 @@ yahoo_doodle_command_send_extra(gc, from, DOODLE_EXTRA_NONE); } -void yahoo_doodle_command_got_confirm(PurpleConnection *gc, const char *from) +static void yahoo_doodle_command_got_confirm(PurpleConnection *gc, const char *from) { PurpleAccount *account; PurpleWhiteboard *wb; @@ -361,14 +327,14 @@ /* TODO Combine the following IF's? */ /* Check if we requested a doodle session */ - if(wb->state == DOODLE_STATE_REQUESTING) + /*if(wb->state == DOODLE_STATE_REQUESTING) { wb->state = DOODLE_STATE_ESTABLISHED; purple_whiteboard_start(wb); yahoo_doodle_command_send_confirm(gc, from); - } + }*/ /* Check if we accepted a request for a doodle session */ if(wb->state == DOODLE_STATE_REQUESTED) @@ -395,25 +361,21 @@ */ wb = purple_whiteboard_get_session(account, from); - /* TODO Ask if user wants to save picture before the session is closed */ - - /* If this session doesn't exist, don't try and kill it */ if(wb == NULL) return; - else - { - purple_whiteboard_destroy(wb); + + /* TODO Ask if user wants to save picture before the session is closed */ - /* yahoo_doodle_command_send_shutdown(gc, from); */ - } + wb->state = DOODLE_STATE_CANCELED; + purple_whiteboard_destroy(wb); } static void yahoo_doodle_command_send_generic(const char *type, PurpleConnection *gc, const char *to, const char *message, - const char *thirteen, - const char *sixtythree, + int command, + const char *imv, const char *sixtyfour) { struct yahoo_data *yd; @@ -428,48 +390,48 @@ yahoo_packet_hash_str(pkt, 49, "IMVIRONMENT"); yahoo_packet_hash_str(pkt, 1, purple_account_get_username(gc->account)); yahoo_packet_hash_str(pkt, 14, message); - yahoo_packet_hash_str(pkt, 13, thirteen); + yahoo_packet_hash_int(pkt, 13, command); yahoo_packet_hash_str(pkt, 5, to); - yahoo_packet_hash_str(pkt, 63, sixtythree ? sixtythree : "doodle;11"); + yahoo_packet_hash_str(pkt, 63, imv ? imv : DOODLE_IMV_KEY); yahoo_packet_hash_str(pkt, 64, sixtyfour); yahoo_packet_hash_str(pkt, 1002, "1"); yahoo_packet_send_and_free(pkt, yd); } +void yahoo_doodle_command_send_ready(PurpleConnection *gc, const char *to) +{ + yahoo_doodle_command_send_generic("Ready", gc, to, "1", DOODLE_CMD_READY, NULL, "1"); +} + void yahoo_doodle_command_send_request(PurpleConnection *gc, const char *to) { - yahoo_doodle_command_send_generic("Request", gc, to, "1", "1", NULL, "1"); -} - -void yahoo_doodle_command_send_ready(PurpleConnection *gc, const char *to) -{ - yahoo_doodle_command_send_generic("Ready", gc, to, "", "0", NULL, "0"); + yahoo_doodle_command_send_generic("Request", gc, to, "", DOODLE_CMD_REQUEST, NULL, "0"); } void yahoo_doodle_command_send_draw(PurpleConnection *gc, const char *to, const char *message) { - yahoo_doodle_command_send_generic("Draw", gc, to, message, "3", NULL, "1"); + yahoo_doodle_command_send_generic("Draw", gc, to, message, DOODLE_CMD_DRAW, NULL, "1"); } void yahoo_doodle_command_send_clear(PurpleConnection *gc, const char *to) { - yahoo_doodle_command_send_generic("Clear", gc, to, " ", "2", NULL, "1"); + yahoo_doodle_command_send_generic("Clear", gc, to, " ", DOODLE_CMD_CLEAR, NULL, "1"); } void yahoo_doodle_command_send_extra(PurpleConnection *gc, const char *to, const char *message) { - yahoo_doodle_command_send_generic("Extra", gc, to, message, "4", NULL, "1"); + yahoo_doodle_command_send_generic("Extra", gc, to, message, DOODLE_CMD_EXTRA, NULL, "1"); } void yahoo_doodle_command_send_confirm(PurpleConnection *gc, const char *to) { - yahoo_doodle_command_send_generic("Confirm", gc, to, "1", "5", NULL, "1"); + yahoo_doodle_command_send_generic("Confirm", gc, to, "1", DOODLE_CMD_CONFIRM, NULL, "1"); } void yahoo_doodle_command_send_shutdown(PurpleConnection *gc, const char *to) { - yahoo_doodle_command_send_generic("Shutdown", gc, to, "", "0", ";0", "0"); + yahoo_doodle_command_send_generic("Shutdown", gc, to, "", DOODLE_CMD_SHUTDOWN, ";0", "0"); } void yahoo_doodle_start(PurpleWhiteboard *wb) @@ -491,7 +453,7 @@ /* g_debug_debug("yahoo", "doodle: yahoo_doodle_end()\n"); */ - if (gc) + if (gc && wb->state != DOODLE_STATE_CANCELED) yahoo_doodle_command_send_shutdown(gc, wb->who); g_free(wb->proto_data); @@ -530,7 +492,7 @@ g_return_if_fail(draw_list != NULL); message = yahoo_doodle_build_draw_string(ds, draw_list); - yahoo_doodle_command_send_draw(wb->account->gc, wb->who, message); + yahoo_doodle_command_send_draw(wb->account->gc, wb->who, message); g_free(message); } @@ -604,3 +566,37 @@ purple_whiteboard_set_brush(wb, size, color); } +void yahoo_doodle_process(PurpleConnection *gc, const char *me, const char *from, + const char *command, const char *message) +{ + if(!command) + return; + + /* Now check to see what sort of Doodle message it is */ + switch(atoi(command)) + { + case DOODLE_CMD_REQUEST: + yahoo_doodle_command_got_request(gc, from); + break; + + case DOODLE_CMD_READY: + yahoo_doodle_command_got_ready(gc, from); + break; + + case DOODLE_CMD_CLEAR: + yahoo_doodle_command_got_clear(gc, from); + break; + + case DOODLE_CMD_DRAW: + yahoo_doodle_command_got_draw(gc, from, message); + break; + + case DOODLE_CMD_EXTRA: + yahoo_doodle_command_got_extra(gc, from, message); + break; + + case DOODLE_CMD_CONFIRM: + yahoo_doodle_command_got_confirm(gc, from); + break; + } +}
--- a/libpurple/protocols/yahoo/yahoo_doodle.h Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/yahoo/yahoo_doodle.h Sun Aug 12 20:48:34 2007 +0000 @@ -31,17 +31,19 @@ #include "whiteboard.h" #include "cmds.h" +#define DOODLE_IMV_KEY "doodle;103" + /****************************************************************************** * Defines *****************************************************************************/ /* Doodle communication commands */ /* TODO: Should be an enum. */ -#define DOODLE_CMD_REQUEST 0 -#define DOODLE_CMD_READY 1 -#define DOODLE_CMD_CLEAR 2 -#define DOODLE_CMD_DRAW 3 -#define DOODLE_CMD_EXTRA 4 -#define DOODLE_CMD_CONFIRM 5 +#define DOODLE_CMD_REQUEST 0 +#define DOODLE_CMD_CLEAR 1 +#define DOODLE_CMD_DRAW 2 +#define DOODLE_CMD_EXTRA 3 +#define DOODLE_CMD_READY 4 +#define DOODLE_CMD_CONFIRM 5 /* Doodle communication command for shutting down (also 0) */ #define DOODLE_CMD_SHUTDOWN 0 @@ -54,6 +56,7 @@ #define DOODLE_STATE_REQUESTING 0 #define DOODLE_STATE_REQUESTED 1 #define DOODLE_STATE_ESTABLISHED 2 +#define DOODLE_STATE_CANCELED 3 /* Doodle canvas dimensions */ #define DOODLE_CANVAS_WIDTH 368 @@ -104,12 +107,6 @@ const char *command, const char *message); void yahoo_doodle_initiate(PurpleConnection *gc, const char *to); -void yahoo_doodle_command_got_request(PurpleConnection *gc, const char *from); -void yahoo_doodle_command_got_ready(PurpleConnection *gc, const char *from); -void yahoo_doodle_command_got_draw(PurpleConnection *gc, const char *from, const char *message); -void yahoo_doodle_command_got_clear(PurpleConnection *gc, const char *from); -void yahoo_doodle_command_got_extra(PurpleConnection *gc, const char *from, const char *message); -void yahoo_doodle_command_got_confirm(PurpleConnection *gc, const char *from); void yahoo_doodle_command_got_shutdown(PurpleConnection *gc, const char *from); void yahoo_doodle_command_send_request(PurpleConnection *gc, const char *to);
--- a/libpurple/protocols/yahoo/yahoo_filexfer.c Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/yahoo/yahoo_filexfer.c Sun Aug 12 20:48:34 2007 +0000 @@ -484,7 +484,7 @@ if(service != NULL && imv != NULL && !strcmp(service, "IMVIRONMENT")) { /* Check for a Doodle packet and handle it accordingly */ - if(!strcmp(imv, "doodle;11")) + if(strstr(imv, "doodle;") != NULL) yahoo_doodle_process(gc, me, from, command, message); /* If an IMVIRONMENT packet comes without a specific imviroment name */ @@ -622,12 +622,12 @@ { PurpleXfer *xfer; struct yahoo_xfer_data *xfer_data; - + g_return_val_if_fail(who != NULL, NULL); - + xfer_data = g_new0(struct yahoo_xfer_data, 1); xfer_data->gc = gc; - + /* Build the file transfer handle. */ xfer = purple_xfer_new(gc->account, PURPLE_XFER_SEND, who); if (xfer)
--- a/libpurple/protocols/yahoo/yahoo_packet.c Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/yahoo/yahoo_packet.c Sun Aug 12 20:48:34 2007 +0000 @@ -223,6 +223,11 @@ GSList *l = pkt->hash; int pos = 0; + /* This is only called from one place, and the list is + * always backwards */ + + l = g_slist_reverse(l); + while (l) { struct yahoo_pair *pair = l->data; gchar buf[100];
--- a/libpurple/protocols/yahoo/yahoo_packet.h Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/protocols/yahoo/yahoo_packet.h Sun Aug 12 20:48:34 2007 +0000 @@ -98,6 +98,7 @@ YAHOO_SERVICE_AVATAR_UPDATE = 0xc7, YAHOO_SERVICE_VERIFY_ID_EXISTS = 0xc8, YAHOO_SERVICE_AUDIBLE = 0xd0, + YAHOO_SERVICE_CHGRP_15 = 0xe7, YAHOO_SERVICE_STATUS_15 = 0xf0, YAHOO_SERVICE_LIST_15 = 0Xf1, YAHOO_SERVICE_WEBLOGIN = 0x0226, @@ -117,7 +118,7 @@ }; #define YAHOO_WEBMESSENGER_PROTO_VER 0x0065 -#define YAHOO_PROTO_VER 0x000c +#define YAHOO_PROTO_VER 0x000f #define YAHOO_PROTO_VER_JAPAN 0x000c #define YAHOO_PACKET_HDRLEN (4 + 2 + 2 + 2 + 2 + 4 + 4)
--- a/libpurple/stringref.h Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/stringref.h Sun Aug 12 20:48:34 2007 +0000 @@ -1,3 +1,5 @@ +/* TODO: Can we just replace this whole thing with a GCache */ + /** * @file stringref.h Reference-counted immutable strings * @ingroup core
--- a/libpurple/stun.c Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/stun.c Sun Aug 12 20:48:34 2007 +0000 @@ -429,5 +429,4 @@ void purple_stun_init() { purple_prefs_add_string("/purple/network/stun_server", ""); - purple_stun_discover(NULL); }
--- a/libpurple/win32/global.mak Sun Aug 12 04:03:45 2007 +0000 +++ b/libpurple/win32/global.mak Sun Aug 12 20:48:34 2007 +0000 @@ -40,7 +40,6 @@ PIDGIN_IDLETRACK_TOP := $(PIDGIN_TOP)/win32/IdleTracker PIDGIN_PIXMAPS_TOP := $(PIDGIN_TOP)/pixmaps PIDGIN_PLUGINS_TOP := $(PIDGIN_TOP)/plugins -PIDGIN_SOUNDS_TOP := $(PIDGIN_TOP)/sounds PURPLE_PO_TOP := $(PIDGIN_TREE_TOP)/po PURPLE_PROTOS_TOP := $(PURPLE_TOP)/protocols
--- a/pidgin.spec.in Sun Aug 12 04:03:45 2007 +0000 +++ b/pidgin.spec.in Sun Aug 12 20:48:34 2007 +0000 @@ -244,6 +244,7 @@ # Delete files that we don't want to put in any of the RPMs rm -f $RPM_BUILD_ROOT%{_libdir}/finch/*.la +rm -f $RPM_BUILD_ROOT%{_libdir}/gnt/*.la rm -f $RPM_BUILD_ROOT%{_libdir}/pidgin/*.la rm -f $RPM_BUILD_ROOT%{_libdir}/purple-2/*.la rm -f $RPM_BUILD_ROOT%{_libdir}/purple-2/liboscar.so @@ -297,8 +298,6 @@ # files -f file can only take one filename :( cat %{name}.lang >> %{name}-%{version}-purpleplugins -cat %{name}.lang >> %{name}-%{version}-pidginplugins -cat %{name}.lang >> %{name}-%{version}-finchplugins %clean rm -rf %{buildroot} @@ -442,6 +441,8 @@ %doc %{_mandir}/man1/finch.* %{_bindir}/finch %{_libdir}/libgnt.so.* +%{_libdir}/gnt/irssi.so +%{_libdir}/gnt/s.so %files -n finch-devel %defattr(-, root, root)
--- a/pidgin/Makefile.am Sun Aug 12 04:03:45 2007 +0000 +++ b/pidgin/Makefile.am Sun Aug 12 20:48:34 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/Makefile.mingw Sun Aug 12 04:03:45 2007 +0000 +++ b/pidgin/Makefile.mingw Sun Aug 12 20:48:34 2007 +0000 @@ -146,7 +146,6 @@ install: install_shallow all $(MAKE) -C $(PIDGIN_PLUGINS_TOP) -f $(MINGW_MAKEFILE) install $(MAKE) -C $(PIDGIN_PIXMAPS_TOP) -f $(MINGW_MAKEFILE) install - $(MAKE) -C $(PIDGIN_SOUNDS_TOP) -f $(MINGW_MAKEFILE) install $(MAKE) -C $(PIDGIN_IDLETRACK_TOP) -f $(MINGW_MAKEFILE) install win32/pidgin_dll_rc.rc: win32/pidgin_dll_rc.rc.in $(PIDGIN_TREE_TOP)/VERSION
--- a/pidgin/gtkaccount.c Sun Aug 12 04:03:45 2007 +0000 +++ b/pidgin/gtkaccount.c Sun Aug 12 20:48:34 2007 +0000 @@ -1162,6 +1162,11 @@ { const char *screenname; + if (purple_accounts_get_all() == NULL) { + /* We're adding our first account. Be polite and show the buddy list */ + purple_blist_set_visible(TRUE); + } + screenname = gtk_entry_get_text(GTK_ENTRY(dialog->screenname_entry)); account = purple_account_new(screenname, dialog->protocol_id); new = TRUE; @@ -2541,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; @@ -2569,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/gtkblist.c Sun Aug 12 04:03:45 2007 +0000 +++ b/pidgin/gtkblist.c Sun Aug 12 20:48:34 2007 +0000 @@ -3340,7 +3340,7 @@ presence = purple_buddy_get_presence(b); - if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons")) + if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons") && aliased) { if (!selected && purple_presence_is_idle(presence)) {
--- a/pidgin/gtkblist.h Sun Aug 12 04:03:45 2007 +0000 +++ b/pidgin/gtkblist.h Sun Aug 12 20:48:34 2007 +0000 @@ -365,7 +365,7 @@ * * @param buddy The buddy to return markup from * @param selected Whether this buddy is selected. If TRUE, the markup will not change the color. - * @param aliased TRUE to return the appropriate alias of this buddy, FALSE to return its screenname + * @param aliased TRUE to return the appropriate alias of this buddy, FALSE to return its screenname and status information * @return The markup for this buddy */ gchar *pidgin_blist_get_name_markup(PurpleBuddy *buddy, gboolean selected, gboolean aliased);
--- a/pidgin/gtkcellrendererexpander.c Sun Aug 12 04:03:45 2007 +0000 +++ b/pidgin/gtkcellrendererexpander.c Sun Aug 12 20:48:34 2007 +0000 @@ -285,5 +285,5 @@ else gtk_tree_view_expand_row(GTK_TREE_VIEW(widget),path,FALSE); gtk_tree_path_free(path); - return TRUE; + return FALSE; }
--- a/pidgin/gtkconv.c Sun Aug 12 04:03:45 2007 +0000 +++ b/pidgin/gtkconv.c Sun Aug 12 20:48:34 2007 +0000 @@ -187,6 +187,9 @@ static void pidgin_conv_tab_pack(PidginWindow *win, PidginConversation *gtkconv); static gboolean infopane_press_cb(GtkWidget *widget, GdkEventButton *e, PidginConversation *conv); +static void pidgin_conv_set_position_size(PidginWindow *win, int x, int y, + int width, int height); + static GdkColor *get_nick_color(PidginConversation *gtkconv, const char *name) { static GdkColor col; GtkStyle *style = gtk_widget_get_style(gtkconv->imhtml); @@ -210,8 +213,8 @@ * Callbacks **************************************************************************/ -static gint -close_conv_cb(GtkWidget *w, PidginConversation *gtkconv) +static gboolean +close_conv_cb(GtkWidget *w, GdkEventButton *event, PidginConversation *gtkconv) { GList *list = g_list_copy(gtkconv->convs); @@ -1328,7 +1331,7 @@ { PidginWindow *win = data; - close_conv_cb(NULL, PIDGIN_CONVERSATION(pidgin_conv_window_get_active_conversation(win))); + close_conv_cb(NULL, NULL, PIDGIN_CONVERSATION(pidgin_conv_window_get_active_conversation(win))); } static void @@ -5290,7 +5293,7 @@ gtk_font_options_all |= GTK_IMHTML_USE_SMOOTHSCROLLING; if (gtk_text_buffer_get_char_count(gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->imhtml)))) - gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), "<BR>", gtk_font_options_all); + gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), "<BR>", gtk_font_options_all | GTK_IMHTML_NO_SCROLL); /* First message in a conversation. */ if (gtkconv->newday == 0) @@ -5478,7 +5481,7 @@ color, sml_attrib ? sml_attrib : "", mdate, str); } - gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), buf2, gtk_font_options_all); + gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), buf2, gtk_font_options_all | GTK_IMHTML_NO_SCROLL); if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT && !(flags & PURPLE_MESSAGE_SEND)) { @@ -6253,13 +6256,15 @@ (fields & PIDGIN_CONV_SET_TITLE) || (fields & PIDGIN_CONV_TOPIC)) { - char *title; + char *title, *truncate = NULL, truncchar = '\0'; PurpleConvIm *im = NULL; PurpleAccount *account = purple_conversation_get_account(conv); + PurpleBuddy *buddy = NULL; + PurplePresence *p = NULL; char *markup = NULL; AtkObject *accessibility_obj; /* I think this is a little longer than it needs to be but I'm lazy. */ - char style[51]; + char *style, *status_style; if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) im = PURPLE_CONV_IM(conv); @@ -6272,12 +6277,20 @@ else title = g_strdup(purple_conversation_get_title(conv)); + if ((truncate = strchr(title, ' ')) || + (truncate = strchr(title, '@'))) { + truncchar = *truncate; + *truncate = '\0'; + } + if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) { - PurpleBuddy *buddy = purple_find_buddy(account, conv->name); - if (buddy) + buddy = purple_find_buddy(account, conv->name); + if (buddy) { + p = purple_buddy_get_presence(buddy); markup = pidgin_blist_get_name_markup(buddy, FALSE, FALSE); - else + } else { markup = title; + } } else if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) { PurpleConvChat *chat = PURPLE_CONV_CHAT(conv); const char *topic = purple_conv_chat_get_topic(chat); @@ -6293,54 +6306,56 @@ if (title != markup) g_free(markup); - *style = '\0'; - if (!GTK_WIDGET_REALIZED(gtkconv->tab_label)) gtk_widget_realize(gtkconv->tab_label); accessibility_obj = gtk_widget_get_accessible(gtkconv->tab_cont); if (im != NULL && - purple_conv_im_get_typing_state(im) == PURPLE_TYPING) - { + purple_conv_im_get_typing_state(im) == PURPLE_TYPING) { atk_object_set_description(accessibility_obj, _("Typing")); - strncpy(style, "color=\"#4e9a06\"", sizeof(style)); - } - else if (im != NULL && - purple_conv_im_get_typing_state(im) == PURPLE_TYPED) - { + style = "color=\"#4e9a06\""; + } else if (im != NULL && + purple_conv_im_get_typing_state(im) == PURPLE_TYPED) { atk_object_set_description(accessibility_obj, _("Stopped Typing")); - strncpy(style, "color=\"#c4a000\"", sizeof(style)); - } - else if (gtkconv->unseen_state == PIDGIN_UNSEEN_NICK) - { + style = "color=\"#c4a000\""; + } else if (gtkconv->unseen_state == PIDGIN_UNSEEN_NICK) { atk_object_set_description(accessibility_obj, _("Nick Said")); - strncpy(style, "color=\"#204a87\" style=\"italic\" weight=\"bold\"", sizeof(style)); - } - else if (gtkconv->unseen_state == PIDGIN_UNSEEN_TEXT) - { + style = "color=\"#204a87\" weight=\"bold\""; + } else if (gtkconv->unseen_state == PIDGIN_UNSEEN_TEXT) { atk_object_set_description(accessibility_obj, _("Unread Messages")); - strncpy(style, "color=\"#cc0000\" weight=\"bold\"", sizeof(style)); + style = "color=\"#cc0000\" weight=\"bold\""; + } else if (gtkconv->unseen_state == PIDGIN_UNSEEN_EVENT) { + atk_object_set_description(accessibility_obj, _("New Event")); + style = "color=\"#888a85\" weight=\"bold\""; + } else { + style = ""; } - else if (gtkconv->unseen_state == PIDGIN_UNSEEN_EVENT) - { - atk_object_set_description(accessibility_obj, _("New Event")); - strncpy(style, "color=\"#888a85\" style=\"italic\"", sizeof(style)); + + if (p && purple_presence_is_status_primitive_active(p, PURPLE_STATUS_OFFLINE)) { + status_style = "strikethrough='true'"; + } else if (p && !purple_presence_is_status_primitive_active(p, PURPLE_STATUS_AVAILABLE) && + !purple_presence_is_status_primitive_active(p, PURPLE_STATUS_INVISIBLE)) { + status_style = "style='italic'"; + } else { + status_style = ""; } - if (*style != '\0') + if (*style != '\0' || *status_style != '\0') { char *html_title,*label; html_title = g_markup_escape_text(title, -1); - - label = g_strdup_printf("<span %s>%s</span>", - style, html_title); + label = g_strdup_printf("<span %s %s>%s</span>", + style, status_style, html_title); g_free(html_title); gtk_label_set_markup(GTK_LABEL(gtkconv->tab_label), label); g_free(label); } else gtk_label_set_text(GTK_LABEL(gtkconv->tab_label), title); + + if (truncate) + *truncate = truncchar; if (pidgin_conv_window_is_active_conversation(conv)) update_typing_icon(gtkconv); @@ -6584,8 +6599,15 @@ event = gtk_event_box_new(); gtk_container_add(GTK_CONTAINER(gtkconv->u.im->icon_container), event); + gtk_event_box_set_visible_window(GTK_EVENT_BOX(event), FALSE); + gtk_widget_add_events(event, + GDK_POINTER_MOTION_MASK | GDK_LEAVE_NOTIFY_MASK); g_signal_connect(G_OBJECT(event), "button-press-event", G_CALLBACK(icon_menu), gtkconv); + g_signal_connect(G_OBJECT(event), "motion-notify-event", + G_CALLBACK(pidgin_conv_motion_cb), gtkconv); + g_signal_connect(G_OBJECT(event), "leave-notify-event", + G_CALLBACK(pidgin_conv_leave_cb), gtkconv); gtk_widget_show(event); gtkconv->u.im->icon = gtk_image_new_from_pixbuf(scale); @@ -7151,30 +7173,37 @@ purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/conversations/tabs", TRUE); purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/tab_side", GTK_POS_TOP); purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/scrollback_lines", 4000); - purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/x", 0); - purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/y", 0); purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/conversations/use_theme_font", TRUE); purple_prefs_add_string(PIDGIN_PREFS_ROOT "/conversations/custom_font", ""); /* Conversations -> Chat */ purple_prefs_add_none(PIDGIN_PREFS_ROOT "/conversations/chat"); - purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/chat/default_width", 410); - purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/chat/default_height", 160); purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/chat/entry_height", 50); purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/chat/userlist_width", 80); + purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/chat/x", 0); + purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/chat/y", 0); + purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/chat/width", 0); + purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/chat/height", 0); + /* Conversations -> IM */ purple_prefs_add_none(PIDGIN_PREFS_ROOT "/conversations/im"); + purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/im/x", 0); + purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/im/y", 0); + purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/im/width", 0); + purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/im/height", 0); purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/conversations/im/animate_buddy_icons", TRUE); - purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/im/default_width", 410); - purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/im/default_height", 160); purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/im/entry_height", 50); purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/conversations/im/show_buddy_icons", TRUE); purple_prefs_add_string(PIDGIN_PREFS_ROOT "/conversations/im/hide_new", "never"); +#ifdef _WIN32 + purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/win32/minimize_new_convs", FALSE); +#endif + /* Connect callbacks. */ purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/conversations/close_on_tabs", close_on_tabs_pref_cb, NULL); @@ -7467,7 +7496,7 @@ _("Confirm close"), GTK_WINDOW(gtkwin->window), GTK_DIALOG_MODAL, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, - PIDGIN_STOCK_CLOSE_TABS, GTK_RESPONSE_OK, NULL); + GTK_STOCK_CLOSE, GTK_RESPONSE_OK, NULL); gtk_dialog_set_default_response(GTK_DIALOG(warn_close_dialog), GTK_RESPONSE_OK); @@ -7759,7 +7788,7 @@ return FALSE; gtkconv = pidgin_conv_window_get_gtkconv_at_index(win, tab_clicked); - close_conv_cb(NULL, gtkconv); + close_conv_cb(NULL, NULL, gtkconv); return TRUE; } @@ -8018,7 +8047,7 @@ if (gconv != gtkconv) { - close_conv_cb(NULL, gconv); + close_conv_cb(NULL, NULL, gconv); } } } @@ -8030,7 +8059,7 @@ gtkconv = g_object_get_data(menu, "clicked_tab"); if (gtkconv) - close_conv_cb(NULL, gtkconv); + close_conv_cb(NULL, NULL, gtkconv); } static gboolean @@ -8308,13 +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/x") && - y == purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/y") && - event->width == purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/default_width") && - event->height == purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/default_height")) - return FALSE; /* carry on normally */ - /* don't save off-screen positioning */ if (x + event->width < 0 || y + event->height < 0 || @@ -8322,9 +8344,9 @@ y > gdk_screen_height()) return FALSE; /* carry on normally */ - /* store the position */ - purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/x", x); - purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/y", y); + /* store the position */ + purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/im/x", x); + purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/im/y", y); purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/im/width", event->width); purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/im/height", event->height); @@ -8334,35 +8356,38 @@ } static void -pidgin_conv_restore_position(PidginWindow *win) { - int conv_x, conv_y, conv_width, conv_height; - - conv_width = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/width"); - +pidgin_conv_set_position_size(PidginWindow *win, int conv_x, int conv_y, + int conv_width, int conv_height) +{ /* if the window exists, is hidden, we're saving positions, and the * position is sane... */ - if (win && win->window && - !GTK_WIDGET_VISIBLE(win->window) && conv_width != 0) { - - conv_x = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/x"); - conv_y = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/y"); - conv_height = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/height"); - - /* ...check position is on screen... */ - if (conv_x >= gdk_screen_width()) - conv_x = gdk_screen_width() - 100; - else if (conv_x + conv_width < 0) - conv_x = 100; - - if (conv_y >= gdk_screen_height()) - conv_y = gdk_screen_height() - 100; - else if (conv_y + conv_height < 0) - conv_y = 100; - - /* ...and move it back. */ - gtk_window_move(GTK_WINDOW(win->window), conv_x, conv_y); - gtk_window_resize(GTK_WINDOW(win->window), conv_width, conv_height); - } + if (win && win->window && + !GTK_WIDGET_VISIBLE(win->window) && conv_width != 0) { + + /* ...check position is on screen... */ + if (conv_x >= gdk_screen_width()) + conv_x = gdk_screen_width() - 100; + else if (conv_x + conv_width < 0) + conv_x = 100; + + if (conv_y >= gdk_screen_height()) + conv_y = gdk_screen_height() - 100; + else if (conv_y + conv_height < 0) + conv_y = 100; + + /* ...and move it back. */ + gtk_window_move(GTK_WINDOW(win->window), conv_x, conv_y); + gtk_window_resize(GTK_WINDOW(win->window), conv_width, conv_height); + } +} + +static void +pidgin_conv_restore_position(PidginWindow *win) { + pidgin_conv_set_position_size(win, + 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 * @@ -8387,8 +8412,6 @@ g_signal_connect(G_OBJECT(win->window), "delete_event", G_CALLBACK(close_win_cb), win); - g_signal_connect(G_OBJECT(win->window), "configure_event", - G_CALLBACK(gtk_conv_configure_cb), NULL); g_signal_connect(G_OBJECT(win->window), "focus_in_event", G_CALLBACK(focus_win_cb), win); @@ -8517,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) { @@ -8526,7 +8563,6 @@ GtkWidget *close_image; PurpleConversationType conv_type; const gchar *tmp_lab; - gint close_button_width, close_button_height, focus_width, focus_pad; conv_type = purple_conversation_get_type(conv); @@ -8538,29 +8574,19 @@ /* Close button. */ - gtkconv->close = gtk_button_new(); - gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &close_button_width, &close_button_height); - if (gtk_check_version(2, 4, 2) == NULL) { - /* Need to account for extra padding around the gtkbutton */ - gtk_widget_style_get(GTK_WIDGET(gtkconv->close), - "focus-line-width", &focus_width, - "focus-padding", &focus_pad, - NULL); - close_button_width += (focus_width + focus_pad) * 2; - close_button_height += (focus_width + focus_pad) * 2; - } - gtk_widget_set_size_request(GTK_WIDGET(gtkconv->close), - close_button_width, close_button_height); - - gtk_button_set_relief(GTK_BUTTON(gtkconv->close), GTK_RELIEF_NONE); - close_image = gtk_image_new_from_stock(GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU); + gtkconv->close = gtk_event_box_new(); + gtk_event_box_set_visible_window(GTK_EVENT_BOX(gtkconv->close), FALSE); + 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), "clicked", - G_CALLBACK(close_conv_cb), gtkconv); + g_signal_connect(G_OBJECT(gtkconv->close), "button-press-event", + G_CALLBACK(close_conv_cb), gtkconv); #if !GTK_CHECK_VERSION(2,6,0) /* @@ -8581,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); @@ -8605,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); } @@ -8650,8 +8673,8 @@ MIN(g_utf8_strlen(gtk_label_get_text(GTK_LABEL(gtkconv->tab_label)), -1), 12) ); } - if (angle) - gtk_label_set_angle(GTK_LABEL(gtkconv->tab_label), angle); + + gtk_label_set_angle(GTK_LABEL(gtkconv->tab_label), angle); #endif #if 0 @@ -8706,8 +8729,14 @@ !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->icon); */ gtk_widget_show(gtkconv->tab_label); if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/close_on_tabs")) gtk_widget_show(gtkconv->close); @@ -8729,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) @@ -8914,6 +8937,9 @@ if (win == NULL) { win = pidgin_conv_window_new(); + g_signal_connect(G_OBJECT(win->window), "configure_event", + G_CALLBACK(gtk_conv_configure_cb), NULL); + pidgin_conv_window_add_gtkconv(win, conv); pidgin_conv_window_show(win); } else { @@ -8922,6 +8948,53 @@ } /* This one places conversations in the last made window of the same type. */ +static gboolean +conv_placement_last_created_win_type_configured_cb(GtkWidget *w, + GdkEventConfigure *event, PidginConversation *conv) +{ + int x, y; + PurpleConversationType type = purple_conversation_get_type(conv->active_conv); + GList *all; + + if (GTK_WIDGET_VISIBLE(w)) + gtk_window_get_position(GTK_WINDOW(w), &x, &y); + else + return FALSE; /* carry on normally */ + + /* Workaround for GTK+ bug # 169811 - "configure_event" is fired + * when the window is being maximized */ + if (gdk_window_get_state(w->window) & GDK_WINDOW_STATE_MAXIMIZED) + return FALSE; + + /* don't save off-screen positioning */ + if (x + event->width < 0 || + y + event->height < 0 || + x > gdk_screen_width() || + y > gdk_screen_height()) + return FALSE; /* carry on normally */ + + for (all = conv->convs; all != NULL; all = all->next) { + if (type != purple_conversation_get_type(all->data)) { + /* this window has different types of conversation, don't save */ + return FALSE; + } + } + + if (type == PURPLE_CONV_TYPE_IM) { + purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/im/x", x); + purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/im/y", y); + purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/im/width", event->width); + purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/im/height", event->height); + } else if (type == PURPLE_CONV_TYPE_CHAT) { + purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/chat/x", x); + purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/chat/y", y); + purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/chat/width", event->width); + purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/chat/height", event->height); + } + + return FALSE; +} + static void conv_placement_last_created_win_type(PidginConversation *conv) { @@ -8932,8 +9005,26 @@ if (win == NULL) { win = pidgin_conv_window_new(); + if (PURPLE_CONV_TYPE_IM == purple_conversation_get_type(conv->active_conv) || + purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/chat/width") == 0) { + pidgin_conv_set_position_size(win, + 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")); + } else if (PURPLE_CONV_TYPE_CHAT == purple_conversation_get_type(conv->active_conv)) { + pidgin_conv_set_position_size(win, + purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/chat/x"), + purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/chat/y"), + purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/chat/width"), + purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/chat/height")); + } + pidgin_conv_window_add_gtkconv(win, conv); pidgin_conv_window_show(win); + + g_signal_connect(G_OBJECT(win->window), "configure_event", + G_CALLBACK(conv_placement_last_created_win_type_configured_cb), conv); } else pidgin_conv_window_add_gtkconv(win, conv); } @@ -8946,6 +9037,9 @@ win = pidgin_conv_window_new(); + g_signal_connect(G_OBJECT(win->window), "configure_event", + G_CALLBACK(gtk_conv_configure_cb), NULL); + pidgin_conv_window_add_gtkconv(win, conv); pidgin_conv_window_show(win);
--- a/pidgin/gtkdebug.c Sun Aug 12 04:03:45 2007 +0000 +++ b/pidgin/gtkdebug.c Sun Aug 12 20:48:34 2007 +0000 @@ -183,7 +183,7 @@ gtk_container_add(GTK_CONTAINER(GTK_DIALOG(win->find)->vbox), hbox); img = gtk_image_new_from_stock(PIDGIN_STOCK_DIALOG_QUESTION, - GTK_ICON_SIZE_DIALOG); + gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_HUGE)); gtk_box_pack_start(GTK_BOX(hbox), img, FALSE, FALSE, 0); gtk_misc_set_alignment(GTK_MISC(img), 0, 0);
--- a/pidgin/gtkdocklet.c Sun Aug 12 04:03:45 2007 +0000 +++ b/pidgin/gtkdocklet.c Sun Aug 12 20:48:34 2007 +0000 @@ -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/gtkimhtml.c Sun Aug 12 04:03:45 2007 +0000 +++ b/pidgin/gtkimhtml.c Sun Aug 12 20:48:34 2007 +0000 @@ -1037,7 +1037,7 @@ { char *tmp; - if (text == NULL) + if (text == NULL || !(*text)) return; tmp = g_markup_escape_text(text, -1); @@ -1053,7 +1053,7 @@ if (!gtk_text_view_get_editable(GTK_TEXT_VIEW(imhtml))) return; - if (selection_data->length < 0) { + if (imhtml->wbfo || selection_data->length <= 0) { gtk_clipboard_request_text(clipboard, paste_plaintext_received_cb, imhtml); return; } else { @@ -2270,10 +2270,11 @@ GtkIMHtml *imhtml = data; GtkAdjustment *adj = GTK_TEXT_VIEW(imhtml)->vadjustment; gdouble max_val = adj->upper - adj->page_size; + gdouble scroll_val = gtk_adjustment_get_value(adj) + ((max_val - gtk_adjustment_get_value(adj)) / 3); g_return_val_if_fail(imhtml->scroll_time != NULL, FALSE); - if (g_timer_elapsed(imhtml->scroll_time, NULL) > MAX_SCROLL_TIME) { + if (g_timer_elapsed(imhtml->scroll_time, NULL) > MAX_SCROLL_TIME || scroll_val >= max_val) { /* time's up. jump to the end and kill the timer */ gtk_adjustment_set_value(adj, max_val); g_timer_destroy(imhtml->scroll_time); @@ -2282,7 +2283,7 @@ } /* scroll by 1/3rd the remaining distance */ - gtk_adjustment_set_value(adj, gtk_adjustment_get_value(adj) + ((max_val - gtk_adjustment_get_value(adj)) / 3)); + gtk_adjustment_set_value(adj, scroll_val); return TRUE; }
--- a/pidgin/gtkimhtmltoolbar.c Sun Aug 12 04:03:45 2007 +0000 +++ b/pidgin/gtkimhtmltoolbar.c Sun Aug 12 20:48:34 2007 +0000 @@ -815,6 +815,9 @@ gboolean bold, italic, underline; char *tmp; char *tmp2; + GtkLabel *label = g_object_get_data(G_OBJECT(toolbar), "font_label"); + + gtk_label_set_label(label, _("_Font")); gtk_imhtml_get_current_format(GTK_IMHTML(toolbar->imhtml), &bold, &italic, &underline); @@ -822,7 +825,6 @@ if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toolbar->bold)) != bold) toggle_button_set_active_block(GTK_TOGGLE_BUTTON(toolbar->bold), bold, toolbar); - if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toolbar->italic)) != italic) toggle_button_set_active_block(GTK_TOGGLE_BUTTON(toolbar->italic), italic, toolbar); @@ -835,20 +837,57 @@ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toolbar->smaller_size), FALSE); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toolbar->larger_size), FALSE); + if (bold) { + gchar *markup = g_strdup_printf("<b>%s</b>", + gtk_label_get_label(label)); + gtk_label_set_markup_with_mnemonic(label, markup); + g_free(markup); + } + if (italic) { + gchar *markup = g_strdup_printf("<i>%s</i>", + gtk_label_get_label(label)); + gtk_label_set_markup_with_mnemonic(label, markup); + g_free(markup); + } + if (underline) { + gchar *markup = g_strdup_printf("<u>%s</u>", + gtk_label_get_label(label)); + gtk_label_set_markup_with_mnemonic(label, markup); + g_free(markup); + } + tmp = gtk_imhtml_get_current_fontface(GTK_IMHTML(toolbar->imhtml)); toggle_button_set_active_block(GTK_TOGGLE_BUTTON(toolbar->font), (tmp != NULL), toolbar); + if (tmp != NULL) { + gchar *markup = g_strdup_printf("<span font_desc=\"%s\">%s</span>", + tmp, gtk_label_get_label(label)); + gtk_label_set_markup_with_mnemonic(label, markup); + g_free(markup); + } g_free(tmp); tmp = gtk_imhtml_get_current_forecolor(GTK_IMHTML(toolbar->imhtml)); toggle_button_set_active_block(GTK_TOGGLE_BUTTON(toolbar->fgcolor), (tmp != NULL), toolbar); + if (tmp != NULL) { + gchar *markup = g_strdup_printf("<span foreground=\"%s\">%s</span>", + tmp, gtk_label_get_label(label)); + gtk_label_set_markup_with_mnemonic(label, markup); + g_free(markup); + } g_free(tmp); tmp = gtk_imhtml_get_current_backcolor(GTK_IMHTML(toolbar->imhtml)); tmp2 = gtk_imhtml_get_current_background(GTK_IMHTML(toolbar->imhtml)); toggle_button_set_active_block(GTK_TOGGLE_BUTTON(toolbar->bgcolor), (tmp != NULL || tmp2 != NULL), toolbar); + if (tmp != NULL) { + gchar *markup = g_strdup_printf("<span background=\"%s\">%s</span>", + tmp, gtk_label_get_label(label)); + gtk_label_set_markup_with_mnemonic(label, markup); + g_free(markup); + } g_free(tmp); g_free(tmp2); } @@ -920,6 +959,7 @@ gtk_imhtmltoolbar_finalize (GObject *object) { GtkIMHtmlToolbar *toolbar = GTK_IMHTMLTOOLBAR(object); + GtkWidget *menu; if (toolbar->image_dialog != NULL) { @@ -944,6 +984,13 @@ free(toolbar->sml); gtk_object_sink(GTK_OBJECT(toolbar->tooltips)); + menu = g_object_get_data(object, "font_menu"); + if (menu) + gtk_widget_destroy(menu); + menu = g_object_get_data(object, "insert_menu"); + if (menu) + gtk_widget_destroy(menu); + G_OBJECT_CLASS(parent_class)->finalize (object); } @@ -1027,6 +1074,12 @@ g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(insert_smiley_cb), toolbar); toolbar->smiley = button; + + /* Reset formatting */ + button = pidgin_pixbuf_toolbar_button_from_stock(PIDGIN_STOCK_TOOLBAR_SMILEY); + g_signal_connect(G_OBJECT(button), "clicked", + G_CALLBACK(clear_formatting_cb), toolbar); + toolbar->clear = button; } static void @@ -1043,6 +1096,13 @@ g_signal_handlers_unblock_by_func(G_OBJECT(item), G_CALLBACK(gtk_button_clicked), button); } +static void +enable_markup(GtkWidget *widget, gpointer null) +{ + if (GTK_IS_LABEL(widget)) + g_object_set(G_OBJECT(widget), "use-markup", TRUE, NULL); +} + static void gtk_imhtmltoolbar_init (GtkIMHtmlToolbar *toolbar) { GtkWidget *hbox = GTK_WIDGET(toolbar); @@ -1054,25 +1114,29 @@ GtkWidget *font_menu; GtkWidget *insert_menu; GtkWidget *menuitem; - GtkWidget *button; GtkWidget *sep; int i; struct { const char *label; GtkWidget **button; + gboolean check; } buttons[] = { - {_("_Bold"), &toolbar->bold}, - {_("_Italic"), &toolbar->italic}, - {_("_Underline"), &toolbar->underline}, - {_("_Larger"), &toolbar->larger_size}, + {_("<b>_Bold</b>"), &toolbar->bold, TRUE}, + {_("<i>_Italic</i>"), &toolbar->italic, TRUE}, + {_("<u>_Underline</u>"), &toolbar->underline, TRUE}, + {_("<span size='larger'>_Larger</span>"), &toolbar->larger_size, TRUE}, #if 0 - {_("_Normal"), &toolbar->normal_size}, + {_("_Normal"), &toolbar->normal_size, TRUE}, #endif - {_("_Smaller"), &toolbar->smaller_size}, - {_("_Font face"), &toolbar->font}, - {_("_Foreground color"), &toolbar->fgcolor}, - {_("_Background color"), &toolbar->bgcolor}, - {NULL, NULL} + {_("<span size='smaller'>_Smaller</span>"), &toolbar->smaller_size, TRUE}, + /* If we want to show the formatting for the following items, we would + * need to update them when formatting changes. The above items don't need + * no updating nor nothin' */ + {_("_Font face"), &toolbar->font, TRUE}, + {_("Foreground _color"), &toolbar->fgcolor, TRUE}, + {_("Bac_kground color"), &toolbar->bgcolor, TRUE}, + {_("_Reset formatting"), &toolbar->clear, FALSE}, + {NULL, NULL, FALSE} }; @@ -1098,47 +1162,35 @@ image = gtk_image_new_from_stock(GTK_STOCK_BOLD, gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL)); gtk_box_pack_start(GTK_BOX(bbox), image, FALSE, FALSE, 0); label = gtk_label_new_with_mnemonic(_("_Font")); + gtk_label_set_use_markup(GTK_LABEL(label), TRUE); + g_object_set_data(G_OBJECT(hbox), "font_label", label); gtk_box_pack_start(GTK_BOX(bbox), label, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(hbox), font_button, FALSE, FALSE, 0); gtk_widget_show_all(font_button); font_menu = gtk_menu_new(); + g_object_set_data(G_OBJECT(toolbar), "font_menu", font_menu); - for (i = 0; buttons[i].label; i++) { GtkWidget *old = *buttons[i].button; - menuitem = gtk_check_menu_item_new_with_mnemonic(buttons[i].label); + if (buttons[i].check) { + menuitem = gtk_check_menu_item_new_with_mnemonic(buttons[i].label); + g_signal_connect_after(G_OBJECT(old), "toggled", + G_CALLBACK(update_menuitem), menuitem); + } else { + menuitem = gtk_menu_item_new_with_mnemonic(buttons[i].label); + } g_signal_connect_swapped(G_OBJECT(menuitem), "activate", G_CALLBACK(gtk_button_clicked), old); - g_signal_connect_after(G_OBJECT(old), "toggled", - G_CALLBACK(update_menuitem), menuitem); gtk_menu_shell_append(GTK_MENU_SHELL(font_menu), menuitem); g_signal_connect(G_OBJECT(old), "notify::sensitive", G_CALLBACK(button_sensitiveness_changed), menuitem); + gtk_container_foreach(GTK_CONTAINER(menuitem), (GtkCallback)enable_markup, NULL); } - - g_signal_connect(G_OBJECT(font_button), "clicked", G_CALLBACK(pidgin_menu_clicked), font_menu); - g_signal_connect(G_OBJECT(font_menu), "deactivate", G_CALLBACK(pidgin_menu_deactivate), font_button); - - /* Sep */ - sep = gtk_vseparator_new(); - gtk_box_pack_start(GTK_BOX(hbox), sep, FALSE, FALSE, 0); - gtk_widget_show_all(sep); - /* Reset Formatting */ - button = gtk_toggle_button_new(); - gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE); - bbox = gtk_hbox_new(FALSE, 3); - gtk_container_add(GTK_CONTAINER(button), bbox); - image = gtk_image_new_from_stock(PIDGIN_STOCK_CLEAR, gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL)); - gtk_box_pack_start(GTK_BOX(bbox), image, FALSE, FALSE, 0); - label = gtk_label_new_with_mnemonic(_("_Reset font")); - gtk_box_pack_start(GTK_BOX(bbox), label, FALSE, FALSE, 0); - gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0); - gtk_widget_show_all(button); - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(clear_formatting_cb), toolbar); - toolbar->clear = button; + 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 */ sep = gtk_vseparator_new(); @@ -1158,6 +1210,7 @@ gtk_widget_show_all(insert_button); insert_menu = gtk_menu_new(); + g_object_set_data(G_OBJECT(toolbar), "insert_menu", insert_menu); menuitem = gtk_menu_item_new_with_mnemonic(_("_Smiley")); g_signal_connect_swapped(G_OBJECT(menuitem), "activate", G_CALLBACK(gtk_button_clicked), toolbar->smiley); @@ -1177,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), "clicked", 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/gtkmain.c Sun Aug 12 04:03:45 2007 +0000 +++ b/pidgin/gtkmain.c Sun Aug 12 20:48:34 2007 +0000 @@ -31,6 +31,7 @@ #include "eventloop.h" #include "ft.h" #include "log.h" +#include "network.h" #include "notify.h" #include "prefs.h" #include "prpl.h" @@ -694,7 +695,9 @@ return 1; } +#if GLIB_CHECK_VERSION(2,2,0) g_set_application_name(_("Pidgin")); +#endif /* glib-2.0 >= 2.2.0 */ #ifdef _WIN32 winpidgin_init(hint); @@ -760,7 +763,6 @@ #endif return 0; } - /* TODO: Move blist loading into purple_blist_init() */ purple_set_blist(purple_blist_new()); @@ -777,6 +779,10 @@ /* TODO: Move pounces loading into purple_pounces_init() */ purple_pounces_load(); + /* Call this early on to try to auto-detect our IP address and + * hopefully save some time later. + * TODO: move this (back) into purple_core_init() when purple_prefs_load() is in purple_prefs_init() */ + purple_network_get_my_ip(-1); /* HACK BY SEANEGAN: * We've renamed prpl-oscar to prpl-aim and prpl-icq, accordingly.
--- a/pidgin/gtknotify.c Sun Aug 12 04:03:45 2007 +0000 +++ b/pidgin/gtknotify.c Sun Aug 12 20:48:34 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/gtkprefs.c Sun Aug 12 04:03:45 2007 +0000 +++ b/pidgin/gtkprefs.c Sun Aug 12 20:48:34 2007 +0000 @@ -994,7 +994,7 @@ pidgin_prefs_checkbox(_("Show _formatting on incoming messages"), PIDGIN_PREFS_ROOT "/conversations/show_incoming_formatting", vbox); - iconpref1 = pidgin_prefs_checkbox(strchr(_("/Buddies/Show Buddy _Details")+1,'/')+1, + iconpref1 = pidgin_prefs_checkbox(_("Show _detailed information"), PIDGIN_PREFS_ROOT "/conversations/im/show_buddy_icons", vbox); iconpref2 = pidgin_prefs_checkbox(_("Enable buddy ic_on animation"), PIDGIN_PREFS_ROOT "/conversations/im/animate_buddy_icons", vbox); @@ -1947,7 +1947,7 @@ "/purple/away/away_when_idle", vbox); select = pidgin_prefs_labeled_spin_button(vbox, - _("_Minutes before changing status:"), "/purple/away/mins_before_away", + _("_Minutes before becoming idle:"), "/purple/away/mins_before_away", 1, 24 * 60, sg); g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(pidgin_toggle_sensitive), select); @@ -2236,4 +2236,13 @@ purple_prefs_remove(PIDGIN_PREFS_ROOT "/away/queue_messages"); purple_prefs_remove(PIDGIN_PREFS_ROOT "/away"); purple_prefs_remove("/plugins/gtk/docklet/queue_messages"); + + purple_prefs_remove(PIDGIN_PREFS_ROOT "/conversations/chat/default_width"); + purple_prefs_remove(PIDGIN_PREFS_ROOT "/conversations/chat/default_height"); + purple_prefs_remove(PIDGIN_PREFS_ROOT "/conversations/im/default_width"); + purple_prefs_remove(PIDGIN_PREFS_ROOT "/conversations/im/default_height"); + purple_prefs_rename(PIDGIN_PREFS_ROOT "/conversations/x", + PIDGIN_PREFS_ROOT "/conversations/im/x"); + purple_prefs_rename(PIDGIN_PREFS_ROOT "/conversations/y", + PIDGIN_PREFS_ROOT "/conversations/im/y"); }
--- a/pidgin/gtkrequest.c Sun Aug 12 04:03:45 2007 +0000 +++ b/pidgin/gtkrequest.c Sun Aug 12 20:48:34 2007 +0000 @@ -1003,9 +1003,6 @@ if (purple_request_field_list_get_multi_select(field)) gtk_tree_selection_set_mode(sel, GTK_SELECTION_MULTIPLE); - g_signal_connect(G_OBJECT(sel), "changed", - G_CALLBACK(list_field_select_changed_cb), field); - column = gtk_tree_view_column_new(); gtk_tree_view_insert_column(GTK_TREE_VIEW(treeview), column, -1); @@ -1028,6 +1025,17 @@ gtk_tree_selection_select_iter(sel, &iter); } + /* + * We only want to catch changes made by the user, so it's important + * that we wait until after the list is created to connect this + * handler. If we connect the handler before the loop above and + * there are multiple items selected, then selecting the first iter + * in the tree causes list_field_select_changed_cb to be triggered + * which clears out the rest of the list of selected items. + */ + g_signal_connect(G_OBJECT(sel), "changed", + G_CALLBACK(list_field_select_changed_cb), field); + gtk_container_add(GTK_CONTAINER(sw), treeview); gtk_widget_show(treeview);
--- a/pidgin/gtksound.c Sun Aug 12 04:03:45 2007 +0000 +++ b/pidgin/gtksound.c Sun Aug 12 20:48:34 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/gtkutils.c Sun Aug 12 04:03:45 2007 +0000 +++ b/pidgin/gtkutils.c Sun Aug 12 20:48:34 2007 +0000 @@ -2047,11 +2047,11 @@ entry.entry.buddy->account, entry.entry.buddy->name ); +#else + item->data = g_strdup(entry.entry.buddy->name); + g_completion_add_items(data->completion, item); +#endif /* NEW_STYLE_COMPLETION */ } -#else - item->data = g_strdup(buddy->name); - g_completion_add_items(data->completion, item); -#endif /* NEW_STYLE_COMPLETION */ } } }
--- a/pidgin/pidginstock.c Sun Aug 12 04:03:45 2007 +0000 +++ b/pidgin/pidginstock.c Sun Aug 12 20:48:34 2007 +0000 @@ -111,7 +111,7 @@ { PIDGIN_STOCK_STATUS_LOGOUT, "status", "log-out.png", TRUE, TRUE, TRUE, TRUE, FALSE, FALSE , NULL }, { PIDGIN_STOCK_STATUS_OFFLINE, "status", "offline.png", TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, PIDGIN_STOCK_STATUS_OFFLINE_I }, { PIDGIN_STOCK_STATUS_PERSON, "status", "person.png", TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, NULL }, - { PIDGIN_STOCK_STATUS_MESSAGE, "status", "message-pending.png",TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL }, + { PIDGIN_STOCK_STATUS_MESSAGE, "toolbar", "message-new.png",TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL }, { PIDGIN_STOCK_STATUS_IGNORED, "emblems", "blocked.png", TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL }, { PIDGIN_STOCK_STATUS_FOUNDER, "emblems", "founder.png", TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL }, @@ -155,7 +155,7 @@ { PIDGIN_STOCK_TOOLBAR_INSERT_IMAGE, "toolbar", "insert-image.png", TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL }, { PIDGIN_STOCK_TOOLBAR_INSERT_LINK, "toolbar", "insert-link.png", TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL }, { PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW, "toolbar", "message-new.png", TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL }, - { PIDGIN_STOCK_TOOLBAR_PENDING, "status", "message-pending.png", TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL }, + { PIDGIN_STOCK_TOOLBAR_PENDING, "toolbar", "message-new.png", TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL }, { PIDGIN_STOCK_TOOLBAR_PLUGINS, "toolbar", "plugins.png", TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL }, { PIDGIN_STOCK_TOOLBAR_TYPING, "toolbar", "typing.png", TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL }, { PIDGIN_STOCK_TOOLBAR_UNBLOCK, "toolbar", "unblock.png", TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL },
--- a/pidgin/pixmaps/Makefile.am Sun Aug 12 04:03:45 2007 +0000 +++ b/pidgin/pixmaps/Makefile.am Sun Aug 12 20:48:34 2007 +0000 @@ -12,7 +12,7 @@ pidgin.ico pidginbuttonpixdir = $(datadir)/pixmaps/pidgin/buttons -pidginbuttonpix_DATA = edit.png pause.png +pidginbuttonpix_DATA = edit.png pause.png pidgindistpixdir = $(datadir)/pixmaps/pidgin pidgindistpix_DATA = logo.png arrow-down.xpm arrow-left.xpm arrow-right.xpm arrow-up.xpm
--- a/pidgin/pixmaps/emotes/default/22/default.theme.in Sun Aug 12 04:03:45 2007 +0000 +++ b/pidgin/pixmaps/emotes/default/22/default.theme.in Sun Aug 12 20:48:34 2007 +0000 @@ -29,7 +29,7 @@ smile.png :-) :) wink.png ;-) ;) sad.png :-( :( -tongue.png :-P :P +tongue.png :-P :P :-p :p shock.png =-O kiss.png :-* shout.png >:o @@ -90,7 +90,7 @@ good.png (Y) (y) bad.png (N) (n) vampire.png :[ :-[ -goat.png (nah) +goat.png (nah) sun.png (#) rainbow.png (R) (r) quiet.png :-# @@ -122,141 +122,143 @@ party.png <:o) eyeroll.png 8-) yawn.png |-) -goat.png (nah) ! skywalker.png C:-) c:-) C:) c:) ! monkey.png :-(|) + +### Hidden MSN emotes sigarette.png (ci) (CI) handcuffs.png (%) -console.png (xx) (XX) +console.png (xx) (XX) fingers-crossed.png (yn) (YN) ### Following QQ 2006 [QQ] -shock.png /:O /jy /surprised -curl-lip.png /:~ /pz /curl_lip -desire.png /:* /se /desire -dazed.png /:| /dazed -party.png /8-) /dy /revel -crying.png /:< /ll /cry -bashful.png /:$ /hx /bashful -shut-mouth.png /:X /bz /shut_mouth -sleepy.png /:Z /shui /sleep -weep.png /:'( /dk /weep -embarrassed.png /:-| /gg /embarassed -pissed-off.png /:@ /fn /pissed_off -act-up.png /:P /tp /act_up -smile-big.png /:D /cy /toothy_smile -smile.png /:) /wx /small_smile -sad.png /:( /ng /sad -glasses-cool.png /:+ /kuk /cool -doctor.png /:# /feid /SARS -silly.png /:Q /zk /crazy -sick.png /:T /tu /vomit -snicker.png /;p /tx /titter -cute.png /;-D /ka /cute -disdain.png /;d /by /disdain -arrogant.png /;o /am /arrogant -starving.png /:g /jie /starving -yawn.png /|-) /kun /sleepy -terror.png /:! /jk /terror -sweat.png /:L /sweat -smirk.png /:> /hanx /smirk -soldier.png /:; /db /soldier -struggle.png /;f /fendou /struggle -curse.png /:-S /zhm /curse -question.png /? /yiw /question -quiet.png /;x /xu /shh -hypnotized.png /;@ /yun /dizzy -excruciating.png /:8 /zhem /excrutiating -freaked-out.png /;! /shuai /freaked_out -skeleton.png /!!! /kl /skeleton -hammer.png /xx /qiao /hammer -bye.png /bye /zj /bye -go-away.png /go /shan /go -tremble.png /shake /fad /shake -in-love.png /love /aiq /love -jump.png /jump /tiao /jump -search.png /find /zhao /search -lashes.png /& /mm /beautiful_eyebrows -pig.png /pig /zt /pig -cat.png /cat /mm /cat -dog.png /dog /xg /dog -hug-left.png /hug /yb /hug -coins.png /$ /qianc /money -lamp.png /! /dp /lightbulb -bowl.png /cup /bei /cup -cake.png /cake /dg /cake -thunder.png /li /shd /lightning -bomb.png /bome /zhd /bomb -knife.png /kn /dao /knife -soccerball.png /footb /zq /soccer -musical-note.png /music /yy /music -poop.png /shit /bb /shit -coffee.png /coffee /kf /coffee -eat.png /eat /fan /eat -pill.png /pill /yw /pill -rose.png /rose /mg /rose -wilt.png /fade /dx /wilt -kiss.png /kiss /wen /kiss -love.png /heart /xin /heart -love-over.png /break /xs /broken_heart -meeting.png /meeting /hy /meeting -present.png /gift /lw /gift -phone.png /phone /dh /phone -clock.png /time /sj /time -mail.png /email /yj /email -tv.png /TV /ds /TV -sun.png /sun /ty /sun -moon.png /moon /yl /moon -good.png /strong /qiang /thumbs_up -bad.png /weak /ruo /thumbs_down -handshake.png /share /ws /handshake -victory.png /v /shl /victory -beauty.png /<J> /mn /beauty -qq.png /<QQ> /qz /qq -blowkiss.png /<L> /fw /blow_kiss -angry.png /<O> /oh /angry -liquor.png /<B> /bj /baijiu -can.png /<U> /qsh /soda -watermelon.png /<W> /xigua /watermelon -rain.png /<!!> /xy /rain -cloudy.png /<~> /duoy /cloudy -snowman.png /<Z> /xr /snowman -star.png /<*> /xixing /star -girl.png /<00> /nv /woman -boy.png /<11> /nan /man +shock.png /:O /jy /surprised +curl-lip.png /:~ /pz /curl_lip +desire.png /:* /se /desire +dazed.png /:| /dazed +party.png /8-) /dy /revel +crying.png /:< /ll /cry +bashful.png /:$ /hx /bashful +shut-mouth.png /:X /bz /shut_mouth +sleepy.png /:Z /shui /sleep +weep.png /:'( /dk /weep +embarrassed.png /:-| /gg /embarassed +pissed-off.png /:@ /fn /pissed_off +act-up.png /:P /tp /act_up +smile-big.png /:D /cy /toothy_smile +smile.png /:) /wx /small_smile +sad.png /:( /ng /sad +glasses-cool.png /:+ /kuk /cool +doctor.png /:# /feid /SARS +silly.png /:Q /zk /crazy +sick.png /:T /tu /vomit +snicker.png /;p /tx /titter +cute.png /;-D /ka /cute +disdain.png /;d /by /disdain +arrogant.png /;o /am /arrogant +starving.png /:g /jie /starving +yawn.png /|-) /kun /sleepy +terror.png /:! /jk /terror +sweat.png /:L /sweat +smirk.png /:> /hanx /smirk +soldier.png /:; /db /soldier +struggle.png /;f /fendou /struggle +curse.png /:-S /zhm /curse +question.png /? /yiw /question +quiet.png /;x /xu /shh +hypnotized.png /;@ /yun /dizzy +excruciating.png /:8 /zhem /excrutiating +freaked-out.png /;! /shuai /freaked_out +skeleton.png /!!! /kl /skeleton +hammer.png /xx /qiao /hammer +bye.png /bye /zj /bye +go-away.png /go /shan /go +tremble.png /shake /fad /shake +in-love.png /love /aiq /love +jump.png /jump /tiao /jump +search.png /find /zhao /search +lashes.png /& /mm /beautiful_eyebrows +pig.png /pig /zt /pig +cat.png /cat /mm /cat +dog.png /dog /xg /dog +hug-left.png /hug /yb /hug +coins.png /$ /qianc /money +lamp.png /! /dp /lightbulb +bowl.png /cup /bei /cup +cake.png /cake /dg /cake +thunder.png /li /shd /lightning +bomb.png /bome /zhd /bomb +knife.png /kn /dao /knife +soccerball.png /footb /zq /soccer +musical-note.png /music /yy /music +poop.png /shit /bb /shit +coffee.png /coffee /kf /coffee +eat.png /eat /fan /eat +pill.png /pill /yw /pill +rose.png /rose /mg /rose +wilt.png /fade /dx /wilt +kiss.png /kiss /wen /kiss +love.png /heart /xin /heart +love-over.png /break /xs /broken_heart +meeting.png /meeting /hy /meeting +present.png /gift /lw /gift +phone.png /phone /dh /phone +clock.png /time /sj /time +mail.png /email /yj /email +tv.png /TV /ds /TV +sun.png /sun /ty /sun +moon.png /moon /yl /moon +good.png /strong /qiang /thumbs_up +bad.png /weak /ruo /thumbs_down +handshake.png /share /ws /handshake +victory.png /v /shl /victory +beauty.png /<J> /mn /beauty +qq.png /<QQ> /qz /qq +blowkiss.png /<L> /fw /blow_kiss +angry.png /<O> /oh /angry +liquor.png /<B> /bj /baijiu +can.png /<U> /qsh /soda +watermelon.png /<W> /xigua /watermelon +rain.png /<!!> /xy /rain +cloudy.png /<~> /duoy /cloudy +snowman.png /<Z> /xr /snowman +star.png /<*> /xixing /star +girl.png /<00> /nv /woman +boy.png /<11> /nan /man ! skywalker.png C:-) c:-) C:) c:) ! monkey.png :-(|) -### Following ICQ 5.1 +### Following ICQ 6.0 [ICQ] smile.png :-) :) +neutral.png :-$ sad.png :-( :( +shock.png =-O wink.png ;-) ;) -tongue.png :-P :P +tongue.png :-P :P :-p :p +#[:-} laugh.png *JOKINGLY* +sleepy.png *TIRED* crying.png :'( +sick.png :-! #*KISSED* +#*STOP* kiss.png :-* +#*KISSING* embarrassed.png :-[ +devil.png ]:-> angel.png O:-) -shut-mouth.png :-X :X +rose.png @}->-- +shut-mouth.png :-X :X :-x :x +bomb.png @= thinking.png :-\\ :-/ +good.png *THUMBS\ UP* shout.png >:o >:O +beer.png *DRINK* smile-big.png :-D :D moneymouth.png :-$ -shock.png =-O glasses-cool.png 8-) -#[:-} -sleepy.png *TIRED* -sick.png :-! -#*STOP* -#*KISSING* -devil.png ]:-> -rose.png @}->-- -bomb.png @= -good.png *THUMBS\ UP* -beer.png *DRINK* in-love.png *IN\ LOVE* ! skywalker.png C:-) c:-) C:) c:) ! monkey.png :-(|) @@ -312,7 +314,7 @@ love-over.png =(( sweat.png #:-S #:-s rotfl.png =)) -loser.png L-) l-) +#loser L-) l-) MISSING/YAHOO 6: "Loser!" party.png <:-P <:-p nailbiting.png :-SS :-Ss :-sS :-ss cowboy.png <):) @@ -349,8 +351,8 @@ #youkiddingme.png :-j :-J ### These only work in a certain IMvironment -male-fighter.png o-> O-> -#malefighter2.png o=> O=> +#male-fighter1.png o-> O-> +#male-fighter2.png o=> O=> female-fighter.png o-+ O-+ yin-yang.png (%)
--- a/pidgin/pixmaps/icons/22/Makefile.am Sun Aug 12 04:03:45 2007 +0000 +++ b/pidgin/pixmaps/icons/22/Makefile.am Sun Aug 12 20:48:34 2007 +0000 @@ -2,7 +2,7 @@ EXTRA_DIST = pidgin.png -pidginiconspixdir = $(datadir)/icons/hicolor/24x24/apps +pidginiconspixdir = $(datadir)/icons/hicolor/22x22/apps pidginiconspix_DATA = $(EXTRA_DIST)
--- a/pidgin/pixmaps/status/16/Makefile.am Sun Aug 12 04:03:45 2007 +0000 +++ b/pidgin/pixmaps/status/16/Makefile.am Sun Aug 12 20:48:34 2007 +0000 @@ -8,7 +8,6 @@ invisible.png \ log-in.png \ log-out.png \ - message-pending.png \ offline.png \ person.png
--- a/pidgin/plugins/win32/transparency/win2ktrans.c Sun Aug 12 04:03:45 2007 +0000 +++ b/pidgin/plugins/win32/transparency/win2ktrans.c Sun Aug 12 20:48:34 2007 +0000 @@ -274,9 +274,11 @@ gtk_window_get_size(GTK_WINDOW(win), &width, &height); gtk_box_pack_start(GTK_BOX(vbox), slider_box, FALSE, FALSE, 0); +#if 0 /*Now that we save window sizes, don't resize it or else it causes windows to grow*/ /* Make window taller so we don't slowly collapse its message area */ gtk_window_resize(GTK_WINDOW(win), width, (height + slidereq.height)); +#endif /* Add window to list, to track that it has a slider */ slidwin = g_new0(slider_win, 1); slidwin->win = win; @@ -292,6 +294,7 @@ slider_win *slidwin = (slider_win*) tmp->data; if (slidwin != NULL && GTK_IS_WINDOW(slidwin->win)) { +#if 0 GtkRequisition slidereq; gint width, height; /* Figure out how tall the slider was */ @@ -300,12 +303,13 @@ gtk_window_get_size( GTK_WINDOW(slidwin->win), &width, &height); - +#endif gtk_widget_destroy(slidwin->slider); - +#if 0 gtk_window_resize( GTK_WINDOW(slidwin->win), width, (height - slidereq.height)); +#endif } g_free(slidwin); tmp = tmp->next;
--- a/pidgin/sounds/Makefile.am Sun Aug 12 04:03:45 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 Sun Aug 12 04:03:45 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; -
--- a/pidgin/win32/nsis/translations/swedish.nsh Sun Aug 12 04:03:45 2007 +0000 +++ b/pidgin/win32/nsis/translations/swedish.nsh Sun Aug 12 20:48:34 2007 +0000 @@ -1,4 +1,4 @@ -;; +;; ;; swedish.nsh ;; ;; Swedish language strings for the Windows Pidgin NSIS installer. @@ -8,56 +8,53 @@ ;; Author: Peter Hjalmarsson <xake@telia.com>, 2005. ;; Version 3 -; Make sure to update the PIDGIN_MACRO_LANGUAGEFILE_END macro in -; langmacros.nsh when updating this file - ; Startup Checks -!define INSTALLER_IS_RUNNING "Installationsprogrammet körs redan." -!define PIDGIN_IS_RUNNING "En instans av Pidgin körs redan. Avsluta Pidgin och försök igen." -!define GTK_INSTALLER_NEEDED "Körmiljön GTK+ är antingen inte installerat eller behöver uppgraderas.$\rVar god installera v${GTK_MIN_VERSION} eller högre av GTK+-körmiljön." +!define INSTALLER_IS_RUNNING "Installationsprogrammet körs redan." +!define PIDGIN_IS_RUNNING "En instans av Pidgin körs redan. Avsluta Pidgin och försök igen." +!define GTK_INSTALLER_NEEDED "Körmiljön GTK+ är antingen inte installerat eller behöver uppgraderas.$\rVar god installera v${GTK_MIN_VERSION} eller högre av GTK+-körmiljön." ; License Page -!define PIDGIN_LICENSE_BUTTON "Nästa >" -!define PIDGIN_LICENSE_BOTTOM_TEXT "$(^Name) är utgivet under GPL. Licensen finns tillgänglig här för informationssyften enbart. $_CLICK" +!define PIDGIN_LICENSE_BUTTON "Nästa >" +!define PIDGIN_LICENSE_BOTTOM_TEXT "$(^Name) är utgivet under GPL. Licensen finns tillgänglig här för informationssyften enbart. $_CLICK" ; Components Page !define PIDGIN_SECTION_TITLE "Pidgin Snabbmeddelandeklient (obligatorisk)" -!define GTK_SECTION_TITLE "GTK+-körmiljö (obligatorisk)" -!define PIDGIN_SHORTCUTS_SECTION_TITLE "Genvägar" +!define GTK_SECTION_TITLE "GTK+-körmiljö (obligatorisk)" +!define PIDGIN_SHORTCUTS_SECTION_TITLE "Genvägar" !define PIDGIN_DESKTOP_SHORTCUT_SECTION_TITLE "Skrivbord" !define PIDGIN_STARTMENU_SHORTCUT_SECTION_TITLE "Startmeny" -!define PIDGIN_SECTION_DESCRIPTION "Pidgins kärnfiler och DLL:er" -!define GTK_SECTION_DESCRIPTION "En GUI-verktygsuppsättning för flera olika plattformar som Pidgin använder." +!define PIDGIN_SECTION_DESCRIPTION "Pidgins kärnfiler och DLL:er" +!define GTK_SECTION_DESCRIPTION "En GUI-verktygsuppsättning för flera olika plattformar som Pidgin använder." -!define PIDGIN_SHORTCUTS_SECTION_DESCRIPTION "Genvägar för att starta Pidgin" -!define PIDGIN_DESKTOP_SHORTCUT_DESC "Skapar en genväg till Pidgin pÃ¥ skrivbordet" -!define PIDGIN_STARTMENU_SHORTCUT_DESC "Skapar ett tillägg i startmenyn för Pidgin" +!define PIDGIN_SHORTCUTS_SECTION_DESCRIPTION "Genvägar för att starta Pidgin" +!define PIDGIN_DESKTOP_SHORTCUT_DESC "Skapar en genväg till Pidgin på skrivbordet" +!define PIDGIN_STARTMENU_SHORTCUT_DESC "Skapar ett tillägg i startmenyn för Pidgin" ; GTK+ Directory Page -!define GTK_UPGRADE_PROMPT "En äldre version av GTK+ runtime hittades, vill du uppgradera den?$\rOBS! $(^Name) kommer kanske inte att fungera om du inte uppgraderar." +!define GTK_UPGRADE_PROMPT "En äldre version av GTK+ runtime hittades, vill du uppgradera den?$\rOBS! $(^Name) kommer kanske inte att fungera om du inte uppgraderar." ; Installer Finish Page -!define PIDGIN_FINISH_VISIT_WEB_SITE "Besök Windows-Pidgin hemsida" +!define PIDGIN_FINISH_VISIT_WEB_SITE "Besök Windows-Pidgin hemsida" ; Pidgin Section Prompts and Texts -!define PIDGIN_PROMPT_CONTINUE_WITHOUT_UNINSTALL "Kunde inte avinstallera den nuvarande versionen av Pidgin. Den nya versionen kommer att installeras utan att ta bort den för närvarande installerade versionen." +!define PIDGIN_PROMPT_CONTINUE_WITHOUT_UNINSTALL "Kunde inte avinstallera den nuvarande versionen av Pidgin. Den nya versionen kommer att installeras utan att ta bort den för närvarande installerade versionen." ; GTK+ Section Prompts !define GTK_INSTALL_ERROR "Fel vid installation av GTK+ runtime." -!define GTK_BAD_INSTALL_PATH "Den sökväg du angivit gÃ¥r inte att komma Ã¥t eller skapa." +!define GTK_BAD_INSTALL_PATH "Den sökväg du angivit går inte att komma åt eller skapa." ; URL Handler section !define URI_HANDLERS_SECTION_TITLE "URI Hanterare" ; Uninstall Section Prompts -!define un.PIDGIN_UNINSTALL_ERROR_1 "Avinstalleraren kunde inte hitta registervärden för Pidgin.$\rAntagligen har en annan användare installerat applikationen." -!define un.PIDGIN_UNINSTALL_ERROR_2 "Du har inte rättigheter att avinstallera den här applikationen." +!define un.PIDGIN_UNINSTALL_ERROR_1 "Avinstalleraren kunde inte hitta registervärden för Pidgin.$\rAntagligen har en annan användare installerat applikationen." +!define un.PIDGIN_UNINSTALL_ERROR_2 "Du har inte rättigheter att avinstallera den här applikationen." ; Spellcheck Section Prompts -!define PIDGIN_SPELLCHECK_SECTION_TITLE "Stöd för rättstavning" -!define PIDGIN_SPELLCHECK_ERROR "Fel vid installation för rättstavning" -!define PIDGIN_SPELLCHECK_DICT_ERROR "Fel vid installation av rättstavningsordlista" -!define PIDGIN_SPELLCHECK_SECTION_DESCRIPTION "Stöd för Rättstavning. (Internetanslutning krävs för installation)" +!define PIDGIN_SPELLCHECK_SECTION_TITLE "Stöd för rättstavning" +!define PIDGIN_SPELLCHECK_ERROR "Fel vid installation för rättstavning" +!define PIDGIN_SPELLCHECK_DICT_ERROR "Fel vid installation av rättstavningsordlista" +!define PIDGIN_SPELLCHECK_SECTION_DESCRIPTION "Stöd för Rättstavning. (Internetanslutning krävs för installation)" !define ASPELL_INSTALL_FAILED "Installationen misslyckades" !define PIDGIN_SPELLCHECK_BRETON "Bretonska" !define PIDGIN_SPELLCHECK_CATALAN "Katalanska" @@ -69,14 +66,14 @@ !define PIDGIN_SPELLCHECK_ENGLISH "Engelska" !define PIDGIN_SPELLCHECK_ESPERANTO "Esperanto" !define PIDGIN_SPELLCHECK_SPANISH "Spanska" -!define PIDGIN_SPELLCHECK_FAROESE "Färöiska" +!define PIDGIN_SPELLCHECK_FAROESE "Färöiska" !define PIDGIN_SPELLCHECK_FRENCH "Franska" !define PIDGIN_SPELLCHECK_ITALIAN "Italienska" -!define PIDGIN_SPELLCHECK_DUTCH "Nederländska" +!define PIDGIN_SPELLCHECK_DUTCH "Nederländska" !define PIDGIN_SPELLCHECK_NORWEGIAN "Norska" !define PIDGIN_SPELLCHECK_POLISH "Polska" !define PIDGIN_SPELLCHECK_PORTUGUESE "Portugisiska" -!define PIDGIN_SPELLCHECK_ROMANIAN "Rumänska" +!define PIDGIN_SPELLCHECK_ROMANIAN "Rumänska" !define PIDGIN_SPELLCHECK_RUSSIAN "Ryska" !define PIDGIN_SPELLCHECK_SLOVAK "Slovakiska" !define PIDGIN_SPELLCHECK_SWEDISH "Svenska"
--- a/pidgin/win32/winpidgin.c Sun Aug 12 04:03:45 2007 +0000 +++ b/pidgin/win32/winpidgin.c Sun Aug 12 20:48:34 2007 +0000 @@ -332,10 +332,10 @@ break; case LANG_ROMANIAN: posix = "ro"; break; case LANG_RUSSIAN: posix = "ru"; break; - /* LANG_CROATIAN == LANG_SERBIAN == LANG_BOSNIAN */ case LANG_SLOVAK: posix = "sk"; break; case LANG_SLOVENIAN: posix = "sl"; break; case LANG_ALBANIAN: posix = "sq"; break; + /* LANG_CROATIAN == LANG_SERBIAN == LANG_BOSNIAN */ case LANG_SERBIAN: switch (sub_id) { case SUBLANG_SERBIAN_LATIN: @@ -538,6 +538,8 @@ char exe_name[MAX_PATH]; HMODULE hmod; char *tmp; + int pidgin_argc = __argc; + char **pidgin_argv = __argv; /* If debug or help or version flag used, create console for output */ if (strstr(lpszCmdLine, "-d") || strstr(lpszCmdLine, "-h") || strstr(lpszCmdLine, "-v")) { @@ -601,8 +603,20 @@ /* Determine if we're running in portable mode */ if (strstr(lpszCmdLine, "--portable-mode") || (exe_name != NULL && strstr(exe_name, "-portable.exe"))) { + int i = 0, c = 0; + printf("Running in PORTABLE mode.\n"); portable_mode = TRUE; + + /* Remove the --portable-mode arg from the args passed to pidgin so it doesn't choke */ + pidgin_argv = malloc(sizeof(char*) * pidgin_argc); + for (; i < __argc; i++) { + if (strstr(__argv[i], "--portable-mode") == NULL) { + pidgin_argv[c] = __argv[i]; + c++; + } else + pidgin_argc--; + } } if (portable_mode) @@ -635,5 +649,5 @@ return 0; } - return pidgin_main(hInstance, __argc, __argv); + return pidgin_main(hInstance, pidgin_argc, pidgin_argv); }
--- a/po/POTFILES.in Sun Aug 12 04:03:45 2007 +0000 +++ b/po/POTFILES.in Sun Aug 12 20:48:34 2007 +0000 @@ -200,6 +200,7 @@ pidgin/plugins/gevolution/gevolution.c pidgin/plugins/gevolution/gevo-util.c pidgin/plugins/gevolution/new_person_dialog.c +pidgin/plugins/gtkbuddynote.c pidgin/plugins/gtk-signals-test.c pidgin/plugins/history.c pidgin/plugins/iconaway.c
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/share/Makefile.am Sun Aug 12 20:48:34 2007 +0000 @@ -0,0 +1,4 @@ + +SUBDIRS = sounds + +EXTRA_DIST = Makefile.mingw
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/share/Makefile.mingw Sun Aug 12 20:48:34 2007 +0000 @@ -0,0 +1,19 @@ +# +# Makefile.mingw +# +# Description: Makefile for win32 (mingw) version +# + +PIDGIN_TREE_TOP := .. +include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak + +include ./Makefile.am + +.PHONY: install clean + +install: + if test '$(SUBDIRS)'; then \ + list='$(SUBDIRS)'; for subdir in $$list; do \ + $(MAKE) -C $$subdir -f $(MINGW_MAKEFILE) install || exit 1 ;\ + done; \ + fi;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/share/sounds/Makefile.am Sun Aug 12 20:48:34 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 Sun Aug 12 20:48:34 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; +