changeset 15629:2f0f563b3579

merge of '7822ac971c736a7e6545da828b1b79db67ad732f' and 'edc9bd8e3fe860539f170f241e440e214993fcaf'
author Mark Doliner <mark@kingant.net>
date Tue, 13 Feb 2007 07:03:51 +0000
parents 66dc2367b137 (current diff) 2261750cf4e9 (diff)
children 68798ffeb321
files
diffstat 37 files changed, 921 insertions(+), 147 deletions(-) [+]
line wrap: on
line diff
--- a/COPYRIGHT	Tue Feb 13 05:00:49 2007 +0000
+++ b/COPYRIGHT	Tue Feb 13 07:03:51 2007 +0000
@@ -245,6 +245,7 @@
 Havoc Pennington
 Ted Percival
 Eduardo Pérez
+Matt Perry
 Celso Pinto
 Joao Luís Marques Pinto
 Aleksander Piotrowski
--- a/ChangeLog	Tue Feb 13 05:00:49 2007 +0000
+++ b/ChangeLog	Tue Feb 13 07:03:51 2007 +0000
@@ -193,7 +193,7 @@
 	* Sametime protocol support
 	  Requires the meanwhile library: http://meanwhile.sourceforge.net
 	* QQ protocol support (Mark Huetsch, Google Summer of Code)
-	* Removed support for the Napster and TOC protocols
+	* Removed the Napster and TOC protocols plugins
 
 	Other Noteworthy Changes:
 	* UPnP and NAT traversal support (Adam J. Warrington, Google Summer of
@@ -1809,7 +1809,7 @@
 	* Redesigned preferences dialog
 	* Redesigned conversation dialog
 	* Removed the Lag-O-Meter (Lag-O-Meter is now a plugin)
-	* SOCKS 4a/5 proxy works
+	* SOCKS 4/5 proxy works
 	* Buddy Pounces are now saved in .gaimrc
 	* Buddy Chats are now saved in .gaimrc
 	* Ability to merge gaim, aim2, aim4 buddylists. Thanks again bmiller!
@@ -1879,7 +1879,7 @@
 	  lists, unfortunately).  (Thanks Syd)
 	* Font selection dialog
 	* Small changes to the Oscar/libfaim stuff (see libfaim/README.gaim)
-	* SOCKS 4a proxy support
+	* SOCKS 4 proxy support
 	* Better proxy support overall (you can get people's info now! :) )
 	* Two-way file transfer (you can get and send files, but you still
 	  can't initiate either)
--- a/ChangeLog.API	Tue Feb 13 05:00:49 2007 +0000
+++ b/ChangeLog.API	Tue Feb 13 07:03:51 2007 +0000
@@ -256,6 +256,7 @@
 	* gaim_account_supports_offline_message()
 	* gaim_conversation_close_logs(), to force a conversation's log(s) to
 	  be closed.  New logs will be opened as necessary.
+	* gaim_got_protocol_handler_uri()
 	* gaim_plugin_get_id()
 	* gaim_plugin_get_name()
 	* gaim_plugin_get_version()
@@ -417,6 +418,7 @@
 	* "log-displaying"
 	* "savedstatus-changed"
 	* "sendto-extended-menu"
+	* "uri-handler"
 
 	Signals - Removed:
 	* "account-away": replaced by account-status-changed
--- a/configure.ac	Tue Feb 13 05:00:49 2007 +0000
+++ b/configure.ac	Tue Feb 13 07:03:51 2007 +0000
@@ -1901,6 +1901,7 @@
 		   doc/gaim-text.1
 		   m4macros/Makefile
 		   pidgin/Makefile
+		   pidgin/pidgin.pc
 		   pidgin/pixmaps/Makefile
 		   pidgin/pixmaps/animations/Makefile
 		   pidgin/pixmaps/animations/16/Makefile
@@ -1945,7 +1946,9 @@
 		   pidgin/plugins/perl/common/Makefile.PL
 		   pidgin/plugins/ticker/Makefile
 		   pidgin/sounds/Makefile
+		   libpurple/example/Makefile
 		   libpurple/gconf/Makefile
+		   libpurple/purple.pc
 		   libpurple/plugins/Makefile
 		   libpurple/plugins/mono/Makefile
 		   libpurple/plugins/mono/api/Makefile
@@ -1978,6 +1981,7 @@
 		   console/plugins/Makefile
 		   po/Makefile.in
 		   gaim.pc
+		   gaim-uninstalled.pc
 		   gaim.spec
 		  ])
 
--- a/console/libgnt/gnt.pc.in	Tue Feb 13 05:00:49 2007 +0000
+++ b/console/libgnt/gnt.pc.in	Tue Feb 13 07:03:51 2007 +0000
@@ -2,11 +2,12 @@
 exec_prefix=@exec_prefix@
 libdir=@libdir@
 includedir=@includedir@
+datarootdir=@datarootdir@
 datadir=@datadir@
 sysconfdir=@sysconfdir@
  
 Name: LibGNT
-Description: Gaim Ncurses Toolkit is a collection of curses-widgets.
+Description: Glib Ncurses Toolkit is a collection of curses-widgets.
 Version: @VERSION@
 Requires: glib-2.0
 Cflags: -I${includedir}/gnt
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gaim-uninstalled.pc.in	Tue Feb 13 07:03:51 2007 +0000
@@ -0,0 +1,14 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+datarootdir=@datarootdir@
+datadir=@datadir@
+sysconfdir=@sysconfdir@
+ 
+Name: Gaim
+Description: Gaim is a GTK2-based instant messenger application.
+Version: @VERSION@
+Requires: glib-2.0
+Cflags: -I${pc_top_builddir}/${pcfiledir}/libpurple -I${pc_top_builddir}/${pcfiledir}/pidgin
+Libs: ${pc_top_builddir}/${pcfiledir}/libpurple/libpurple.la
--- a/gaim.pc.in	Tue Feb 13 05:00:49 2007 +0000
+++ b/gaim.pc.in	Tue Feb 13 07:03:51 2007 +0000
@@ -2,6 +2,7 @@
 exec_prefix=@exec_prefix@
 libdir=@libdir@
 includedir=@includedir@
+datarootdir=@datarootdir@
 datadir=@datadir@
 sysconfdir=@sysconfdir@
  
--- a/libpurple/Makefile.am	Tue Feb 13 05:00:49 2007 +0000
+++ b/libpurple/Makefile.am	Tue Feb 13 07:03:51 2007 +0000
@@ -6,6 +6,7 @@
 		gaim-send \
 		gaim-send-async \
 		gaim-url-handler \
+		purple.pc.in \
 		Makefile.mingw \
 		win32/global.mak \
 		win32/libc_interface.c \
@@ -23,7 +24,10 @@
 GCONF_DIR=gconf
 endif
 
-SUBDIRS = $(GCONF_DIR) plugins protocols tests
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = purple.pc
+
+SUBDIRS = $(GCONF_DIR) plugins protocols tests . example
 
 gaim_coresources = \
 	account.c \
--- a/libpurple/core.c	Tue Feb 13 05:00:49 2007 +0000
+++ b/libpurple/core.c	Tue Feb 13 07:03:51 2007 +0000
@@ -83,6 +83,13 @@
 	/* The signals subsystem is important and should be first. */
 	gaim_signals_init();
 
+	gaim_signal_register(core, "uri-handler",
+		gaim_marshal_BOOLEAN__POINTER_POINTER_POINTER,
+		gaim_value_new(GAIM_TYPE_BOOLEAN), 3,
+		gaim_value_new(GAIM_TYPE_STRING), /* Protocol */
+		gaim_value_new(GAIM_TYPE_STRING), /* Command */
+		gaim_value_new(GAIM_TYPE_BOXED, "GHashTable *")); /* Parameters */
+
 	gaim_signal_register(core, "quitting", gaim_marshal_VOID, NULL, 0);
 
 	/* The prefs subsystem needs to be initialized before static protocols
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/example/Makefile.am	Tue Feb 13 07:03:51 2007 +0000
@@ -0,0 +1,25 @@
+bin_PROGRAMS = nullclient
+
+nullclient_SOURCES = nullclient.c
+nullclient_DEPENDENCIES = 
+nullclient_LDFLAGS = -export-dynamic
+nullclient_LDADD = \
+	$(DBUS_LIBS) \
+	$(INTLLIBS) \
+	$(GLIB_LIBS) \
+	$(LIBXML_LIBS) \
+	$(top_builddir)/libpurple/libpurple.la
+
+AM_CPPFLAGS = \
+	-DSTANDALONE \
+	-DBR_PTHREADS=0 \
+	-DDATADIR=\"$(datadir)\" \
+	-DLIBDIR=\"$(libdir)/libpurple/\" \
+	-DLOCALEDIR=\"$(datadir)/locale\" \
+	-DSYSCONFDIR=\"$(sysconfdir)\" \
+	-I$(top_srcdir)/libpurple/ \
+	-I$(top_srcdir) \
+	$(DEBUG_CFLAGS) \
+	$(GLIB_CFLAGS) \
+	$(DBUS_CFLAGS) \
+	$(LIBXML_CFLAGS)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/example/defines.h	Tue Feb 13 07:03:51 2007 +0000
@@ -0,0 +1,4 @@
+#define CUSTOM_USER_DIRECTORY  "/dev/null"
+#define CUSTOM_PLUGIN_PATH     ""
+#define PLUGIN_SAVE_PREF       "/gaim/nullclient/plugins/saved"
+#define UI_ID                  "nullclient"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/example/nullclient.c	Tue Feb 13 07:03:51 2007 +0000
@@ -0,0 +1,276 @@
+/*
+ * pidgin
+ *
+ * Pidgin is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * 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 "account.h"
+#include "conversation.h"
+#include "core.h"
+#include "debug.h"
+#include "eventloop.h"
+#include "ft.h"
+#include "log.h"
+#include "notify.h"
+#include "prefix.h"
+#include "prefs.h"
+#include "prpl.h"
+#include "pounce.h"
+#include "savedstatuses.h"
+#include "sound.h"
+#include "status.h"
+#include "util.h"
+#include "whiteboard.h"
+
+#include <glib.h>
+
+#include <string.h>
+#include <unistd.h>
+
+#include "defines.h"
+
+/**
+ * The following eventloop functions are used in both pidgin and gaim-text. If your
+ * application uses glib mainloop, you can safely use this verbatim.
+ */
+#define GAIM_GLIB_READ_COND  (G_IO_IN | G_IO_HUP | G_IO_ERR)
+#define GAIM_GLIB_WRITE_COND (G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL)
+
+typedef struct _GaimGLibIOClosure {
+	GaimInputFunction function;
+	guint result;
+	gpointer data;
+} GaimGLibIOClosure;
+
+static void gaim_glib_io_destroy(gpointer data)
+{
+	g_free(data);
+}
+
+static gboolean gaim_glib_io_invoke(GIOChannel *source, GIOCondition condition, gpointer data)
+{
+	GaimGLibIOClosure *closure = data;
+	GaimInputCondition gaim_cond = 0;
+
+	if (condition & GAIM_GLIB_READ_COND)
+		gaim_cond |= GAIM_INPUT_READ;
+	if (condition & GAIM_GLIB_WRITE_COND)
+		gaim_cond |= GAIM_INPUT_WRITE;
+
+	closure->function(closure->data, g_io_channel_unix_get_fd(source),
+			  gaim_cond);
+
+	return TRUE;
+}
+
+static guint glib_input_add(gint fd, GaimInputCondition condition, GaimInputFunction function,
+							   gpointer data)
+{
+	GaimGLibIOClosure *closure = g_new0(GaimGLibIOClosure, 1);
+	GIOChannel *channel;
+	GIOCondition cond = 0;
+
+	closure->function = function;
+	closure->data = data;
+
+	if (condition & GAIM_INPUT_READ)
+		cond |= GAIM_GLIB_READ_COND;
+	if (condition & GAIM_INPUT_WRITE)
+		cond |= GAIM_GLIB_WRITE_COND;
+
+	channel = g_io_channel_unix_new(fd);
+	closure->result = g_io_add_watch_full(channel, G_PRIORITY_DEFAULT, cond,
+					      gaim_glib_io_invoke, closure, gaim_glib_io_destroy);
+
+	g_io_channel_unref(channel);
+	return closure->result;
+}
+
+static GaimEventLoopUiOps glib_eventloops = 
+{
+	g_timeout_add,
+	g_source_remove,
+	glib_input_add,
+	g_source_remove
+};
+/*** End of the eventloop functions. ***/
+
+/*** Conversation uiops ***/
+static void
+null_write_conv(GaimConversation *conv, const char *who, const char *alias,
+			const char *message, GaimMessageFlags flags, time_t mtime)
+{
+	const char *name;
+	if (alias && *alias)
+		name = alias;
+	else if (who && *who)
+		name = who;
+	else
+		name = NULL;
+
+	printf("(%s) %s %s: %s\n", gaim_conversation_get_name(conv),
+			gaim_utf8_strftime("(%H:%M:%S)", localtime(&mtime)),
+			name, message);
+}
+
+static GaimConversationUiOps null_conv_uiops = 
+{
+	.write_conv = null_write_conv
+};
+
+static void
+null_ui_init()
+{
+	/**
+	 * This should initialize the UI components for all the modules. Here we
+	 * just initialize the UI for conversations.
+	 */
+	gaim_conversations_set_ui_ops(&null_conv_uiops);
+}
+
+static GaimCoreUiOps null_core_uiops = 
+{
+	NULL,
+	NULL,
+	null_ui_init,
+	NULL
+};
+
+static void
+init_libpurple()
+{
+	/* Set a custom user directory (optional) */
+	gaim_util_set_user_dir(CUSTOM_USER_DIRECTORY);
+
+	/* We do not want any debugging for now to keep the noise to a minimum. */
+	gaim_debug_set_enabled(FALSE);
+
+	/* Set the core-uiops, which is used to
+	 * 	- initialize the ui specific preferences.
+	 * 	- initialize the debug ui.
+	 * 	- initialize the ui components for all the modules.
+	 * 	- uninitialize the ui components for all the modules when the core terminates.
+	 */
+	gaim_core_set_ui_ops(&null_core_uiops);
+
+	/* Set the uiops for the eventloop. If your client is glib-based, you can safely
+	 * copy this verbatim. */
+	gaim_eventloop_set_ui_ops(&glib_eventloops);
+
+	/* Set path to search for plugins. The core (libpurple) takes care of loading the
+	 * core-plugins, which includes the protocol-plugins. So it is not essential to add
+	 * any path here, but it might be desired, especially for ui-specific plugins. */
+	gaim_plugins_add_search_path(CUSTOM_PLUGIN_PATH);
+
+	/* Now that all the essential stuff has been set, let's try to init the core. It's
+	 * necessary to provide a non-NULL name for the current ui to the core. This name
+	 * is used by stuff that depends on this ui, for example the ui-specific plugins. */
+	if (!gaim_core_init(UI_ID)) {
+		/* Initializing the core failed. Terminate. */
+		fprintf(stderr,
+				"libpurple initialization failed. Dumping core.\n"
+				"Please report this!\n");
+		abort();
+	}
+
+	/* Create and load the buddylist. */
+	gaim_set_blist(gaim_blist_new());
+	gaim_blist_load();
+
+	/* Load the preferences. */
+	gaim_prefs_load();
+
+	/* Load the desired plugins. The client should save the list of loaded plugins in
+	 * the preferences using gaim_plugins_save_loaded(PLUGIN_SAVE_PREF) */
+	gaim_plugins_load_saved(PLUGIN_SAVE_PREF);
+
+	/* Load the pounces. */
+	gaim_pounces_load();
+}
+
+static void
+signed_on(GaimConnection *gc, gpointer null)
+{
+	GaimAccount *account = gaim_connection_get_account(gc);
+	printf("Account connected: %s %s\n", account->username, account->protocol_id);
+}
+
+static void
+connect_to_signals_for_demonstration_purposes_only()
+{
+	static int handle;
+	gaim_signal_connect(gaim_connections_get_handle(), "signed-on", &handle,
+				GAIM_CALLBACK(signed_on), NULL);
+}
+
+int main()
+{
+	GList *iter;
+	int i, num;
+	GList *names = NULL;
+	const char *prpl;
+	char name[128];
+	char *password;
+	GMainLoop *loop = g_main_loop_new(NULL, FALSE);
+	GaimAccount *account;
+	GaimSavedStatus *status;
+
+	init_libpurple();
+
+	printf("libpurple initialized.\n");
+
+	iter = gaim_plugins_get_protocols();
+	for (i = 0; iter; iter = iter->next) {
+		GaimPlugin *plugin = iter->data;
+		GaimPluginInfo *info = plugin->info;
+		if (info && info->name) {
+			printf("\t%d: %s\n", i++, info->name);
+			names = g_list_append(names, info->id);
+		}
+	}
+	printf("Select the protocol [0-%d]: ", i-1);
+	fgets(name, sizeof(name), stdin);
+	sscanf(name, "%d", &num);
+	prpl = g_list_nth_data(names, num);
+
+	printf("Username: ");
+	fgets(name, sizeof(name), stdin);
+	name[strlen(name) - 1] = 0;  /* strip the \n at the end */
+
+	/* Create the account */
+	account = gaim_account_new(name, prpl);
+
+	/* Get the password for the account */
+	password = getpass("Password: ");
+	gaim_account_set_password(account, password);
+
+	/* It's necessary to enable the account first. */
+	gaim_account_set_enabled(account, UI_ID, TRUE);
+
+	/* Now, to connect the account(s), create a status and activate it. */
+	status = gaim_savedstatus_new(NULL, GAIM_STATUS_AVAILABLE);
+	gaim_savedstatus_activate(status);
+
+	connect_to_signals_for_demonstration_purposes_only();
+
+	g_main_loop_run(loop);
+
+	return 0;
+}
+
--- a/libpurple/log.c	Tue Feb 13 05:00:49 2007 +0000
+++ b/libpurple/log.c	Tue Feb 13 07:03:51 2007 +0000
@@ -1016,7 +1016,9 @@
 gboolean gaim_log_common_is_deletable(GaimLog *log)
 {
 	GaimLogCommonLoggerData *data;
+#ifndef _WIN32
 	gchar *dirname;
+#endif
 
 	g_return_val_if_fail(log != NULL, FALSE);
 
@@ -1435,7 +1437,7 @@
 
 static char *txt_logger_read(GaimLog *log, GaimLogReadFlags *flags)
 {
-	char *read, *minus_header, *minus_header2;
+	char *read, *minus_header;
 	GaimLogCommonLoggerData *data = log->logger_data;
 	*flags = 0;
 	if (!data || !data->path)
@@ -1745,7 +1747,7 @@
 {
 	struct old_logger_data *data = log->logger_data;
 	FILE *file = g_fopen(gaim_stringref_value(data->pathref), "rb");
-	char *tmp, *read = g_malloc(data->length + 1);
+	char *read = g_malloc(data->length + 1);
 	fseek(file, data->offset, SEEK_SET);
 	fread(read, data->length, 1, file);
 	fclose(file);
--- a/libpurple/plugins/perl/common/Makefile.mingw	Tue Feb 13 05:00:49 2007 +0000
+++ b/libpurple/plugins/perl/common/Makefile.mingw	Tue Feb 13 07:03:51 2007 +0000
@@ -12,7 +12,7 @@
 EXTUTILS ?= C:/perl/lib/ExtUtils
 PERL_PLUGIN_TOP := ..
 
-CFLAGS += -Wno-comment
+CFLAGS += -Wno-comment -Wno-unused
 
 ##
 ## INCLUDE PATHS
--- a/libpurple/protocols/msn/msn.c	Tue Feb 13 05:00:49 2007 +0000
+++ b/libpurple/protocols/msn/msn.c	Tue Feb 13 07:03:51 2007 +0000
@@ -35,6 +35,7 @@
 #include "state.h"
 #include "util.h"
 #include "cmds.h"
+#include "core.h"
 #include "prpl.h"
 #include "msn-utils.h"
 #include "version.h"
@@ -1928,6 +1929,71 @@
 	return TRUE;
 }
 
+static GaimAccount *find_acct(const char *prpl, const char *acct_id)
+{
+	GaimAccount *acct = NULL;
+
+	/* If we have a specific acct, use it */
+	if (acct_id) {
+		acct = gaim_accounts_find(acct_id, prpl);
+		if (acct && !gaim_account_is_connected(acct))
+			acct = NULL;
+	} else { /* Otherwise find an active account for the protocol */
+		GList *l = gaim_accounts_get_all();
+		while (l) {
+			if (!strcmp(prpl, gaim_account_get_protocol_id(l->data))
+					&& gaim_account_is_connected(l->data)) {
+				acct = l->data;
+				break;
+			}
+			l = l->next;
+		}
+	}
+
+	return acct;
+}
+
+static gboolean msn_uri_handler(const char *proto, const char *cmd, GHashTable *params)
+{
+	char *acct_id = g_hash_table_lookup(params, "account");
+	GaimAccount *acct;
+
+	if (g_ascii_strcasecmp(proto, "msnim"))
+		return FALSE;
+
+	acct = find_acct("prpl-msn", acct_id);
+
+	if (!acct)
+		return FALSE;
+
+	/* msnim:chat?contact=user@domain.tld */
+	if (!g_ascii_strcasecmp(cmd, "Chat")) {
+		char *sname = g_hash_table_lookup(params, "contact");
+		if (sname) {
+			GaimConversation *conv = gaim_find_conversation_with_account(
+				GAIM_CONV_TYPE_IM, sname, acct);
+			if (conv == NULL)
+				conv = gaim_conversation_new(GAIM_CONV_TYPE_IM, acct, sname);
+			gaim_conversation_present(conv);
+		}
+		/*else
+			**If pidgindialogs_im() was in the core, we could use it here.
+			 * It is all gaim_request_* based, but I'm not sure it really belongs in the core
+			pidgindialogs_im();*/
+
+		return TRUE;
+	}
+	/* msnim:add?contact=user@domain.tld */
+	else if (!g_ascii_strcasecmp(cmd, "Add")) {
+		char *name = g_hash_table_lookup(params, "contact");
+		gaim_blist_request_add_buddy(acct, name, NULL, NULL);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+
 static GaimPluginProtocolInfo prpl_info =
 {
 	OPT_PROTO_MAIL_CHECK,
@@ -2055,6 +2121,9 @@
 	                  _("nudge: nudge a user to get their attention"), NULL);
 
 	gaim_prefs_remove("/plugins/prpl/msn");
+
+	gaim_signal_connect(gaim_get_core(), "uri-handler", plugin,
+		GAIM_CALLBACK(msn_uri_handler), NULL);
 }
 
 GAIM_INIT_PLUGIN(msn, init_plugin, info);
--- a/libpurple/protocols/oscar/family_oservice.c	Tue Feb 13 05:00:49 2007 +0000
+++ b/libpurple/protocols/oscar/family_oservice.c	Tue Feb 13 07:03:51 2007 +0000
@@ -779,8 +779,8 @@
 	return 1;
 }
 
-/*
- * Subtype 0x001e - Set various account settings (mostly ICQ related).
+/**
+ * Subtype 0x001e - Extended Status/Extra Info.
  *
  * These settings are transient, not server-stored (i.e. they only
  * apply to this session, and must be re-set the next time you sign
@@ -791,36 +791,76 @@
  * if your status is visible on ICQ web sites, and you can set
  * your IP address info and what not.
  *
+ * You can also set your "available" message.  This is currently
+ * only supported by iChat, Gaim and other 3rd party clients.
+ *
  * These are the same TLVs seen in user info.  You can
  * also set 0x0008 and 0x000c.
- *
- * TODO: Combine this with the function below.
  */
 int
-aim_srv_setextstatus(OscarData *od, guint32 status)
+aim_srv_setextrainfo(OscarData *od,
+		gboolean seticqstatus, guint32 icqstatus,
+		gboolean setavailmsg, const char *availmsg, const char *itmsurl)
 {
 	FlapConnection *conn;
 	FlapFrame *frame;
 	aim_snacid_t snacid;
 	aim_tlvlist_t *tl = NULL;
-	guint32 data;
 
 	if (!od || !(conn = flap_connection_findbygroup(od, SNAC_FAMILY_ICBM)))
 		return -EINVAL;
 
-	data = AIM_ICQ_STATE_HIDEIP | AIM_ICQ_STATE_DIRECTREQUIREAUTH | status;
+	if (seticqstatus)
+	{
+		aim_tlvlist_add_32(&tl, 0x0006, icqstatus |
+				AIM_ICQ_STATE_HIDEIP | AIM_ICQ_STATE_DIRECTREQUIREAUTH);
+	}
+
+#if 0
+	if (other_stuff_that_isnt_implemented)
+	{
+		aim_tlvlist_add_raw(&tl, 0x000c, 0x0025,
+				chunk_of_x25_bytes_with_ip_address_etc);
+		aim_tlvlist_add_raw(&tl, 0x0011, 0x0005, unknown 0x01 61 10 f6 41);
+		aim_tlvlist_add_16(&tl, 0x0012, unknown 0x00 00);
+	}
+#endif
+
+	if (setavailmsg)
+	{
+		int availmsglen, itmsurllen;
+		ByteStream tmpbs;
+
+		availmsglen = (availmsg != NULL) ? strlen(availmsg) : 0;
+		itmsurllen = (itmsurl != NULL) ? strlen(itmsurl) : 0;
 
-	frame = flap_frame_new(od, 0x02, 10 + 8);
+		byte_stream_new(&tmpbs, availmsglen + 8 + itmsurllen + 8);
+		byte_stream_put16(&tmpbs, 0x0002);
+		byte_stream_put8(&tmpbs, 0x04); /* Flags */
+		byte_stream_put8(&tmpbs, availmsglen + 4);
+		byte_stream_put16(&tmpbs, availmsglen);
+		if (availmsglen > 0)
+			byte_stream_putstr(&tmpbs, availmsg);
+		byte_stream_put16(&tmpbs, 0x0000);
+
+		byte_stream_put16(&tmpbs, 0x0009);
+		byte_stream_put8(&tmpbs, 0x04); /* Flags */
+		byte_stream_put8(&tmpbs, itmsurllen + 4);
+		byte_stream_put16(&tmpbs, itmsurllen);
+		if (itmsurllen > 0)
+			byte_stream_putstr(&tmpbs, itmsurl);
+		byte_stream_put16(&tmpbs, 0x0000);
+
+		aim_tlvlist_add_raw(&tl, 0x001d,
+				byte_stream_curpos(&tmpbs), tmpbs.data);
+		g_free(tmpbs.data);
+	}
+
+	frame = flap_frame_new(od, 0x02, 10 + aim_tlvlist_size(&tl));
 
 	snacid = aim_cachesnac(od, 0x0001, 0x001e, 0x0000, NULL, 0);
 	aim_putsnac(&frame->data, 0x0001, 0x001e, 0x0000, snacid);
 
-	aim_tlvlist_add_32(&tl, 0x0006, data);
-#if 0
-	aim_tlvlist_add_raw(&tl, 0x000c, 0x0025, chunk_of_x25_bytes_with_ip_address_etc);
-	aim_tlvlist_add_raw(&tl, 0x0011, 0x0005, unknown 0x01 61 10 f6 41);
-	aim_tlvlist_add_16(&tl, 0x0012, unknown 0x00 00);
-#endif
 	aim_tlvlist_write(&frame->data, &tl);
 	aim_tlvlist_free(&tl);
 
@@ -829,54 +869,7 @@
 	return 0;
 }
 
-/*
- * Subtype 0x001e - Extended Status.
- *
- * Sets your "available" message.  This is currently only supported by iChat
- * and Gaim.
- *
- * These are the same TLVs seen in user info.  You can
- * also set 0x0008 and 0x000c.
- *
- * TODO: Combine this with the above function.
- */
-int
-aim_srv_setstatusmsg(OscarData *od, const char *msg)
-{
-	FlapConnection *conn;
-	FlapFrame *frame;
-	aim_snacid_t snacid;
-	int msglen;
-
-	if (!od || !(conn = flap_connection_findbygroup(od, 0x0004)))
-		return -EINVAL;
-
-	if (msg == NULL)
-		msglen = 0;
-	else
-		msglen = strlen(msg);
-
-	frame = flap_frame_new(od, 0x02, 10 + 4 + msglen + 8);
-
-	snacid = aim_cachesnac(od, 0x0001, 0x001e, 0x0000, NULL, 0);
-	aim_putsnac(&frame->data, 0x0001, 0x001e, 0x0000, snacid);
-
-	byte_stream_put16(&frame->data, 0x001d); /* userinfo TLV type */
-	byte_stream_put16(&frame->data, msglen + 8); /* total length of userinfo TLV data */
-	byte_stream_put16(&frame->data, 0x0002);
-	byte_stream_put8(&frame->data, 0x04);
-	byte_stream_put8(&frame->data, msglen+4);
-	byte_stream_put16(&frame->data, msglen);
-	if (msglen > 0)
-		byte_stream_putstr(&frame->data, msg);
-	byte_stream_put16(&frame->data, 0x0000);
-
-	flap_connection_send(conn, frame);
-
-	return 0;
-}
-
-/*
+/**
  * Starting this past week (26 Mar 2001, say), AOL has started sending
  * this nice little extra SNAC.  AFAIK, it has never been used until now.
  *
--- a/libpurple/protocols/oscar/libaim.c	Tue Feb 13 05:00:49 2007 +0000
+++ b/libpurple/protocols/oscar/libaim.c	Tue Feb 13 07:03:51 2007 +0000
@@ -128,24 +128,7 @@
 static void
 init_plugin(GaimPlugin *plugin)
 {
-	GaimAccountOption *option;
-
-	option = gaim_account_option_string_new(_("Server"), "server", OSCAR_DEFAULT_LOGIN_SERVER);
-	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
-
-	option = gaim_account_option_int_new(_("Port"), "port", OSCAR_DEFAULT_LOGIN_PORT);
-	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
-
-	option = gaim_account_option_bool_new(
-		_("Always use AIM/ICQ proxy server for file transfers\n(slower, but does not reveal your IP address)"), "always_use_rv_proxy",
-		OSCAR_DEFAULT_ALWAYS_USE_RV_PROXY);
-	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
-
-	/* Preferences */
-	gaim_prefs_add_none("/plugins/prpl/oscar");
-	gaim_prefs_add_bool("/plugins/prpl/oscar/recent_buddies", FALSE);
-	gaim_prefs_add_bool("/plugins/prpl/oscar/show_idle", FALSE);
-	gaim_prefs_remove("/plugins/prpl/oscar/always_use_rv_proxy");
+	oscar_init(GAIM_PLUGIN_PROTOCOL_INFO(plugin));
 }
 
 GAIM_INIT_PLUGIN(aim, init_plugin, info);
--- a/libpurple/protocols/oscar/libicq.c	Tue Feb 13 05:00:49 2007 +0000
+++ b/libpurple/protocols/oscar/libicq.c	Tue Feb 13 07:03:51 2007 +0000
@@ -130,25 +130,10 @@
 {
 	GaimAccountOption *option;
 
-	option = gaim_account_option_string_new(_("Server"), "server", OSCAR_DEFAULT_LOGIN_SERVER);
-	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
-
-	option = gaim_account_option_int_new(_("Port"), "port", OSCAR_DEFAULT_LOGIN_PORT);
-	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+	oscar_init(GAIM_PLUGIN_PROTOCOL_INFO(plugin));
 
 	option = gaim_account_option_string_new(_("Encoding"), "encoding", OSCAR_DEFAULT_CUSTOM_ENCODING);
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
-
-	option = gaim_account_option_bool_new(
-		_("Always use ICQ proxy server for file transfers\n(slower, but does not reveal your IP address)"), "always_use_rv_proxy",
-		OSCAR_DEFAULT_ALWAYS_USE_RV_PROXY);
-	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
-
-	/* Preferences */
-	gaim_prefs_add_none("/plugins/prpl/oscar");
-	gaim_prefs_add_bool("/plugins/prpl/oscar/recent_buddies", FALSE);
-	gaim_prefs_add_bool("/plugins/prpl/oscar/show_idle", FALSE);
-	gaim_prefs_remove("/plugins/prpl/oscar/always_use_rv_proxy");
 }
 
 GAIM_INIT_PLUGIN(icq, init_plugin, info);
--- a/libpurple/protocols/oscar/oscar.c	Tue Feb 13 05:00:49 2007 +0000
+++ b/libpurple/protocols/oscar/oscar.c	Tue Feb 13 07:03:51 2007 +0000
@@ -1786,14 +1786,14 @@
 		GaimBuddy *b = gaim_find_buddy(account, info->sn);
 		GaimStatus *status;
 		const char *active_status_id;
-		
+
 		status = gaim_presence_get_active_status(gaim_buddy_get_presence(b));
 		active_status_id = gaim_status_get_id(status);
-		
+
 		if (!active_status_id || strcmp(active_status_id, status_id))
 			gaim_prpl_got_user_status(account, info->sn, status_id, NULL);
 	}
-	
+
 	/* Login time stuff */
 	if (info->present & AIM_USERINFO_PRESENT_ONLINESINCE)
 		signon = info->onlinesince;
@@ -3569,7 +3569,7 @@
 	GaimConnection *gc;
 	GaimAccount *account;
 	GaimStatus *status;
-	const char *message;
+	const char *message, *itmsurl;
 	char *tmp;
 	va_list ap;
 	guint16 maxpermits, maxdenies;
@@ -3605,7 +3605,8 @@
 	else
 		message = NULL;
 	tmp = gaim_markup_strip_html(message);
-	aim_srv_setstatusmsg(od, tmp);
+	itmsurl = gaim_status_get_attr_string(status, "itmsurl");
+	aim_srv_setextrainfo(od, FALSE, 0, TRUE, tmp, itmsurl);
 	g_free(tmp);
 
 	aim_srv_setidle(od, 0);
@@ -4411,7 +4412,7 @@
 	else if (!strcmp(status_id, OSCAR_STATUS_ID_CUSTOM))
 		data |= AIM_ICQ_STATE_OUT | AIM_ICQ_STATE_AWAY;
 
-	aim_srv_setextstatus(od, data);
+	aim_srv_setextrainfo(od, TRUE, data, FALSE, NULL, NULL);
 }
 
 static void
@@ -4477,7 +4478,7 @@
 	}
 	else if (primitive == GAIM_STATUS_AVAILABLE)
 	{
-		const char *status_html;
+		const char *status_html, *itmsurl;
 		char *status_text = NULL;
 
 		status_html = gaim_status_get_attr_string(status, "message");
@@ -4491,8 +4492,9 @@
 				strcpy(tmp, "...");
 			}
 		}
-
-		aim_srv_setstatusmsg(od, status_text);
+		itmsurl = gaim_status_get_attr_string(status, "itmsurl");
+
+		aim_srv_setextrainfo(od, FALSE, 0, TRUE, status_text, itmsurl);
 		g_free(status_text);
 
 		/* This is needed for us to un-set any previous away message. */
@@ -5702,6 +5704,8 @@
 										   OSCAR_STATUS_ID_AVAILABLE,
 										   NULL, TRUE, TRUE, FALSE,
 										   "message", _("Message"),
+										   gaim_value_new(GAIM_TYPE_STRING),
+										   "itmsurl", _("iTunes Music Store Link"),
 										   gaim_value_new(GAIM_TYPE_STRING), NULL);
 	status_types = g_list_prepend(status_types, type);
 
@@ -6514,3 +6518,130 @@
 	return (od->icq && aim_sn_is_icq(gaim_account_get_username(account)));
 }
 
+/* TODO: Find somewhere to put this instead of including it in a bunch of places.
+ * Maybe just change gaim_accounts_find() to return anything for the prpl if there is no acct_id.
+ */
+static GaimAccount *find_acct(const char *prpl, const char *acct_id)
+{
+	GaimAccount *acct = NULL;
+
+	/* If we have a specific acct, use it */
+	if (acct_id) {
+		acct = gaim_accounts_find(acct_id, prpl);
+		if (acct && !gaim_account_is_connected(acct))
+			acct = NULL;
+	} else { /* Otherwise find an active account for the protocol */
+		GList *l = gaim_accounts_get_all();
+		while (l) {
+			if (!strcmp(prpl, gaim_account_get_protocol_id(l->data))
+					&& gaim_account_is_connected(l->data)) {
+				acct = l->data;
+				break;
+			}
+			l = l->next;
+		}
+	}
+
+	return acct;
+}
+
+
+static gboolean oscar_uri_handler(const char *proto, const char *cmd, GHashTable *params)
+{
+	char *acct_id = g_hash_table_lookup(params, "account");
+	char prpl[11];
+	GaimAccount *acct;
+
+	if (g_ascii_strcasecmp(proto, "aim") && g_ascii_strcasecmp(proto, "icq"))
+		return FALSE;
+
+	g_snprintf(prpl, sizeof(prpl), "prpl-%s", proto);
+
+	acct = find_acct(prpl, acct_id);
+
+	if (!acct)
+		return FALSE;
+
+	/* aim:GoIM?screenname=SCREENNAME&message=MESSAGE */
+	if (!g_ascii_strcasecmp(cmd, "GoIM")) {
+		char *sname = g_hash_table_lookup(params, "screenname");
+		if (sname) {
+			char *message = g_hash_table_lookup(params, "message");
+
+			GaimConversation *conv = gaim_find_conversation_with_account(
+				GAIM_CONV_TYPE_IM, sname, acct);
+			if (conv == NULL)
+				conv = gaim_conversation_new(GAIM_CONV_TYPE_IM, acct, sname);
+			gaim_conversation_present(conv);
+
+			if (message) {
+				/* Spaces are encoded as '+' */
+				g_strdelimit(message, "+", ' ');
+				gaim_conv_im_send(GAIM_CONV_IM(conv), message);
+			}
+		}
+		/*else
+			**If pidgindialogs_im() was in the core, we could use it here.
+			 * It is all gaim_request_* based, but I'm not sure it really belongs in the core
+			pidgindialogs_im();*/
+
+		return TRUE;
+	}
+	/* aim:GoChat?roomname=CHATROOMNAME&exchange=4 */
+	else if (!g_ascii_strcasecmp(cmd, "GoChat")) {
+		char *rname = g_hash_table_lookup(params, "roomname");
+		if (rname) {
+			/* This is somewhat hacky, but the params aren't useful after this command */
+			g_hash_table_insert(params, g_strdup("exchange"), g_strdup("4"));
+			g_hash_table_insert(params, g_strdup("room"), g_strdup(rname));
+			serv_join_chat(gaim_account_get_connection(acct), params);
+		}
+		/*else
+			** Same as above (except that this would have to be re-written using gaim_request_*)
+			pidgin_blist_joinchat_show(); */
+
+		return TRUE;
+	}
+	/* aim:AddBuddy?screenname=SCREENNAME&groupname=GROUPNAME*/
+	else if (!g_ascii_strcasecmp(cmd, "AddBuddy")) {
+		char *sname = g_hash_table_lookup(params, "screenname");
+		char *gname = g_hash_table_lookup(params, "groupname");
+		gaim_blist_request_add_buddy(acct, sname, gname, NULL);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+void oscar_init(GaimPluginProtocolInfo *prpl_info)
+{
+	GaimAccountOption *option;
+	static gboolean init = FALSE;
+
+	option = gaim_account_option_string_new(_("Server"), "server", OSCAR_DEFAULT_LOGIN_SERVER);
+	prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option);
+
+	option = gaim_account_option_int_new(_("Port"), "port", OSCAR_DEFAULT_LOGIN_PORT);
+	prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option);
+
+	option = gaim_account_option_bool_new(
+		_("Always use ICQ proxy server for file transfers\n(slower, but does not reveal your IP address)"), "always_use_rv_proxy",
+		OSCAR_DEFAULT_ALWAYS_USE_RV_PROXY);
+	prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option);
+
+	if (init)
+		return;
+	init = TRUE;
+
+	/* Preferences */
+	gaim_prefs_add_none("/plugins/prpl/oscar");
+	gaim_prefs_add_bool("/plugins/prpl/oscar/recent_buddies", FALSE);
+	gaim_prefs_add_bool("/plugins/prpl/oscar/show_idle", FALSE);
+	gaim_prefs_remove("/plugins/prpl/oscar/always_use_rv_proxy");
+
+	/* protocol handler */
+	/* TODO: figure out a good instance to use here */
+	gaim_signal_connect(gaim_get_core(), "uri-handler", &init,
+		GAIM_CALLBACK(oscar_uri_handler), NULL);
+}
+
--- a/libpurple/protocols/oscar/oscar.h	Tue Feb 13 05:00:49 2007 +0000
+++ b/libpurple/protocols/oscar/oscar.h	Tue Feb 13 07:03:51 2007 +0000
@@ -625,8 +625,7 @@
 /* 0x0014 */ void aim_srv_setprivacyflags(OscarData *od, FlapConnection *conn, guint32);
 /* 0x0016 */ void aim_srv_nop(OscarData *od, FlapConnection *conn);
 /* 0x0017 */ void aim_srv_setversions(OscarData *od, FlapConnection *conn);
-/* 0x001e */ int aim_srv_setstatusmsg(OscarData *od, const char *msg);
-/* 0x001e */ int aim_srv_setextstatus(OscarData *od, guint32 status);
+/* 0x001e */ int aim_srv_setextrainfo(OscarData *od, gboolean seticqstatus, guint32 icqstatus, gboolean setavailmsg, const char *availmsg, const char *itmsurl);
 
 
 void aim_bos_reqrights(OscarData *od, FlapConnection *conn);
--- a/libpurple/protocols/oscar/oscarcommon.h	Tue Feb 13 05:00:49 2007 +0000
+++ b/libpurple/protocols/oscar/oscarcommon.h	Tue Feb 13 07:03:51 2007 +0000
@@ -87,3 +87,4 @@
 GaimXfer *oscar_new_xfer(GaimConnection *gc, const char *who);
 gboolean oscar_offline_message(const GaimBuddy *buddy);
 GList *oscar_actions(GaimPlugin *plugin, gpointer context);
+void oscar_init(GaimPluginProtocolInfo *prpl_info);
--- a/libpurple/protocols/yahoo/yahoo.c	Tue Feb 13 05:00:49 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoo.c	Tue Feb 13 07:03:51 2007 +0000
@@ -28,6 +28,7 @@
 #include "blist.h"
 #include "cipher.h"
 #include "cmds.h"
+#include "core.h"
 #include "debug.h"
 #include "notify.h"
 #include "privacy.h"
@@ -3794,6 +3795,107 @@
 	                 _("doodle: Request user to start a Doodle session"), NULL);
 }
 
+static GaimAccount *find_acct(const char *prpl, const char *acct_id)
+{
+	GaimAccount *acct = NULL;
+
+	/* If we have a specific acct, use it */
+	if (acct_id) {
+		acct = gaim_accounts_find(acct_id, prpl);
+		if (acct && !gaim_account_is_connected(acct))
+			acct = NULL;
+	} else { /* Otherwise find an active account for the protocol */
+		GList *l = gaim_accounts_get_all();
+		while (l) {
+			if (!strcmp(prpl, gaim_account_get_protocol_id(l->data))
+					&& gaim_account_is_connected(l->data)) {
+				acct = l->data;
+				break;
+			}
+			l = l->next;
+		}
+	}
+
+	return acct;
+}
+
+/* This may not be the best way to do this, but we find the first key w/o a value
+ * and assume it is the screenname */
+static void yahoo_find_uri_novalue_param(gpointer key, gpointer value, gpointer user_data)
+{
+	char **retval = user_data;
+
+	if (value == NULL && *retval == NULL) {
+		*retval = key;
+	}
+}
+
+static gboolean yahoo_uri_handler(const char *proto, const char *cmd, GHashTable *params)
+{
+	char *acct_id = g_hash_table_lookup(params, "account");
+	GaimAccount *acct;
+
+	if (g_ascii_strcasecmp(proto, "ymsgr"))
+		return FALSE;
+
+	acct = find_acct(gaim_plugin_get_id(my_protocol), acct_id);
+
+	if (!acct)
+		return FALSE;
+
+	/* ymsgr:SendIM?screename&m=The+Message */
+	if (!g_ascii_strcasecmp(cmd, "SendIM")) {
+		char *sname = NULL;
+		g_hash_table_foreach(params, yahoo_find_uri_novalue_param, &sname);
+		if (sname) {
+			char *message = g_hash_table_lookup(params, "m");
+
+			GaimConversation *conv = gaim_find_conversation_with_account(
+				GAIM_CONV_TYPE_IM, sname, acct);
+			if (conv == NULL)
+				conv = gaim_conversation_new(GAIM_CONV_TYPE_IM, acct, sname);
+			gaim_conversation_present(conv);
+
+			if (message) {
+				/* Spaces are encoded as '+' */
+				g_strdelimit(message, "+", ' ');
+				gaim_conv_im_send(GAIM_CONV_IM(conv), message);
+			}
+		}
+		/*else
+			**If pidgindialogs_im() was in the core, we could use it here.
+			 * It is all gaim_request_* based, but I'm not sure it really belongs in the core
+			pidgindialogs_im();*/
+
+		return TRUE;
+	}
+	/* ymsgr:Chat?roomname */
+	else if (!g_ascii_strcasecmp(cmd, "Chat")) {
+		char *rname = NULL;
+		g_hash_table_foreach(params, yahoo_find_uri_novalue_param, &rname);
+		if (rname) {
+			/* This is somewhat hacky, but the params aren't useful after this command */
+			g_hash_table_insert(params, g_strdup("room"), g_strdup(rname));
+			g_hash_table_insert(params, g_strdup("type"), g_strdup("Chat"));
+			serv_join_chat(gaim_account_get_connection(acct), params);
+		}
+		/*else
+			** Same as above (except that this would have to be re-written using gaim_request_*)
+			pidgin_blist_joinchat_show(); */
+
+		return TRUE;
+	}
+	/* ymsgr:AddFriend?name */
+	else if (!g_ascii_strcasecmp(cmd, "AddFriend")) {
+		char *name = NULL;
+		g_hash_table_foreach(params, yahoo_find_uri_novalue_param, &name);
+		gaim_blist_request_add_buddy(acct, name, NULL, NULL);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
 static GaimWhiteboardPrplOps yahoo_whiteboard_prpl_ops =
 {
 	yahoo_doodle_start,
@@ -3950,6 +4052,9 @@
 	my_protocol = plugin;
 	yahoogaim_register_commands();
 	yahoo_init_colorht();
+
+	gaim_signal_connect(gaim_get_core(), "uri-handler", plugin,
+		GAIM_CALLBACK(yahoo_uri_handler), NULL);
 }
 
 GAIM_INIT_PLUGIN(yahoo, init_plugin, info);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/purple.pc.in	Tue Feb 13 07:03:51 2007 +0000
@@ -0,0 +1,14 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+datadir=@datadir@
+sysconfdir=@sysconfdir@
+ 
+Name: libpurple
+Description: libpurple is a GLib-based instant messenger library.
+Version: @VERSION@
+Requires: glib-2.0
+Cflags: -I${includedir}/libpurple
+Libs: -L${libdir} -lpurple
+
--- a/libpurple/upnp.c	Tue Feb 13 05:00:49 2007 +0000
+++ b/libpurple/upnp.c	Tue Feb 13 07:03:51 2007 +0000
@@ -352,7 +352,7 @@
 			start = start ? start + 3 : baseURL;
 			path = strchr(start, '/');
 			length = path ? path - baseURL : strlen(baseURL);
-			controlURL = g_strdup_printf("%.*s%s", length, baseURL, tmp);
+			controlURL = g_strdup_printf("%.*s%s", (int)length, baseURL, tmp);
 		} else {
 			controlURL = g_strdup_printf("%s%s", baseURL, tmp);
 		}
--- a/libpurple/util.c	Tue Feb 13 05:00:49 2007 +0000
+++ b/libpurple/util.c	Tue Feb 13 07:03:51 2007 +0000
@@ -23,6 +23,7 @@
 #include "internal.h"
 
 #include "conversation.h"
+#include "core.h"
 #include "debug.h"
 #include "notify.h"
 #include "prpl.h"
@@ -2995,6 +2996,69 @@
 /**************************************************************************
  * URI/URL Functions
  **************************************************************************/
+
+void gaim_got_protocol_handler_uri(const char *uri)
+{
+	char proto[11];
+	const char *tmp, *param_string;
+	char *cmd;
+	GHashTable *params = NULL;
+	int len;
+
+	if (!(tmp = strchr(uri, ':')) || tmp == uri) {
+		gaim_debug_error("util", "Malformed protocol handler message - missing protocol.\n");
+		return;
+	}
+
+	len = MIN(sizeof(proto) - 1, (tmp - uri));
+
+	strncpy(proto, uri, len);
+	proto[len] = '\0';
+
+	tmp++;
+	gaim_debug_info("util", "Processing message '%s' for protocol '%s'.\n", tmp, proto);
+
+	if ((param_string = strchr(tmp, '?'))) {
+		const char *keyend = NULL, *pairstart;
+		char *key, *value = NULL;
+
+		cmd = g_strndup(tmp, (param_string - tmp));
+		param_string++;
+
+		params = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+		pairstart = tmp = param_string;
+
+		while (*tmp || *pairstart) {
+			if (*tmp == '&' || !(*tmp)) {
+				/* If there is no explicit value */
+				if (keyend == NULL)
+					keyend = tmp;
+
+				if (keyend && keyend != pairstart) {
+					key = g_strndup(pairstart, (keyend - pairstart));
+					/* If there is an explicit value */
+					if (keyend != tmp && keyend != (tmp - 1))
+						value = g_strndup(keyend + 1, (tmp - keyend - 1));
+					g_hash_table_insert(params, key, value);
+				}
+				keyend = value = NULL;
+				pairstart = (*tmp) ? tmp + 1 : tmp;
+			} else if (*tmp == '=')
+				keyend = tmp;
+
+			if (*tmp)
+				tmp++;
+		}
+	} else
+		cmd = g_strdup(tmp);
+
+	gaim_signal_emit_return_1(gaim_get_core(), "uri-handler", proto, cmd, params);
+
+	g_free(cmd);
+	if (params)
+		g_hash_table_destroy(params);
+}
+
 gboolean
 gaim_url_parse(const char *url, char **ret_host, int *ret_port,
 			   char **ret_path, char **ret_user, char **ret_passwd)
--- a/libpurple/util.h	Tue Feb 13 05:00:49 2007 +0000
+++ b/libpurple/util.h	Tue Feb 13 07:03:51 2007 +0000
@@ -817,6 +817,8 @@
 /**************************************************************************/
 /*@{*/
 
+void gaim_got_protocol_handler_uri(const char *uri);
+
 /**
  * Parses a URL, returning its host, port, file path, username and password.
  *
--- a/pidgin/Makefile.am	Tue Feb 13 05:00:49 2007 +0000
+++ b/pidgin/Makefile.am	Tue Feb 13 07:03:51 2007 +0000
@@ -3,6 +3,7 @@
 		getopt.h \
 		getopt1.c \
 		Makefile.mingw \
+		pidgin.pc.in \
 		win32/IdleTracker/Makefile.mingw \
 		win32/IdleTracker/idletrack.c \
 		win32/IdleTracker/idletrack.h \
@@ -57,6 +58,9 @@
 
 if ENABLE_GTK
 
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = pidgin.pc
+
 SUBDIRS = pixmaps plugins sounds
 
 bin_PROGRAMS = pidgin
--- a/pidgin/Makefile.mingw	Tue Feb 13 05:00:49 2007 +0000
+++ b/pidgin/Makefile.mingw	Tue Feb 13 07:03:51 2007 +0000
@@ -135,7 +135,7 @@
 all: $(EXE_TARGET).exe $(GTKGAIM_TARGET).dll
 	$(MAKE) -C $(GAIM_GTK_PLUGINS_TOP) -f $(GAIM_WIN32_MAKEFILE)
 
-./win32/pidgin_exe_rc.rc: ./win32/pidgin_exe_rc.rc.in $(GAIM_TOP)/VERSION
+win32/pidgin_exe_rc.rc: win32/pidgin_exe_rc.rc.in $(GAIM_TOP)/VERSION
 	sed -e 's/@GAIM_VERSION@/$(GAIM_VERSION)/g' \
 	    -e 's/@ORIGINAL_FILENAME@/$(EXE_NAME)/' \
 	    $@.in > $@
@@ -150,7 +150,7 @@
 	$(MAKE) -C $(GAIM_GTK_SOUNDS_TOP) -f $(GAIM_WIN32_MAKEFILE) install
 	$(MAKE) -C $(GAIM_GTK_IDLETRACK_TOP) -f $(GAIM_WIN32_MAKEFILE) install
 
-./win32/pidgin_dll_rc.rc: ./win32/pidgin_dll_rc.rc.in $(GAIM_TOP)/VERSION
+win32/pidgin_dll_rc.rc: win32/pidgin_dll_rc.rc.in $(GAIM_TOP)/VERSION
 	sed -e 's/@GAIM_VERSION@/$(GAIM_VERSION)/g' \
 	    $@.in > $@
 
--- a/pidgin/gtkblist.c	Tue Feb 13 05:00:49 2007 +0000
+++ b/pidgin/gtkblist.c	Tue Feb 13 07:03:51 2007 +0000
@@ -302,7 +302,7 @@
 											   chat->account);
 
 	if (conv != NULL)
-		pidgin_conv_present_conversation(conv);
+		gaim_conversation_present(conv);
 
 	serv_join_chat(chat->account->gc, chat->components);
 }
@@ -3549,7 +3549,7 @@
 			convs = pidgin_conversations_find_unseen_list(GAIM_CONV_TYPE_IM,
 															PIDGIN_UNSEEN_TEXT, TRUE, 1);
 			if (convs) {
-				pidgin_conv_present_conversation((GaimConversation*)convs->data);
+				gaim_conversation_present((GaimConversation*)convs->data);
 				g_list_free(convs);
 			}
 			break;
--- a/pidgin/gtkconv.c	Tue Feb 13 05:00:49 2007 +0000
+++ b/pidgin/gtkconv.c	Tue Feb 13 07:03:51 2007 +0000
@@ -4932,7 +4932,9 @@
 
 
 	/* TODO: These colors should not be hardcoded so log.c can use them */
-	if (flags & GAIM_MESSAGE_SYSTEM) {
+	if (flags & GAIM_MESSAGE_RAW) {
+		gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), message, gtk_font_options_all);
+	} else if (flags & GAIM_MESSAGE_SYSTEM) {
 		g_snprintf(buf2, sizeof(buf2),
 			   "<FONT %s><FONT SIZE=\"2\"><!--(%s) --></FONT><B>%s</B></FONT>",
 			   sml_attrib ? sml_attrib : "", mdate, displaying);
@@ -4952,8 +4954,6 @@
 			   sml_attrib ? sml_attrib : "", displaying);
 
 		gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), buf2, gtk_font_options_all);
-	} else if (flags & GAIM_MESSAGE_RAW) {
-		gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), displaying, gtk_font_options_all);
 	} else {
 		char *new_message = g_memdup(displaying, length);
 		char *alias_escaped = (alias ? g_markup_escape_text(alias, strlen(alias)) : g_strdup(""));
@@ -7255,7 +7255,7 @@
 	GtkWidget *page;
 	GtkWidget *tab;
 
-	if (e->button == 2) {
+	if (e->button == 2 && e->type == GDK_BUTTON_PRESS) {
 		PidginConversation *gtkconv;
 		tab_clicked = pidgin_conv_get_tab_at_xy(win, e->x_root, e->y_root, NULL);
 
--- a/pidgin/gtkdialogs.c	Tue Feb 13 05:00:49 2007 +0000
+++ b/pidgin/gtkdialogs.c	Tue Feb 13 07:03:51 2007 +0000
@@ -724,7 +724,7 @@
 	if (conv == NULL)
 		conv = gaim_conversation_new(GAIM_CONV_TYPE_IM, account, username);
 
-	pidgin_conv_present_conversation(conv);
+	gaim_conversation_present(conv);
 }
 
 static gboolean
--- a/pidgin/gtkdocklet.c	Tue Feb 13 05:00:49 2007 +0000
+++ b/pidgin/gtkdocklet.c	Tue Feb 13 07:03:51 2007 +0000
@@ -587,7 +587,7 @@
 			if (status == DOCKLET_STATUS_ONLINE_PENDING || status == DOCKLET_STATUS_AWAY_PENDING) {
 				GList *l = get_pending_list(1);
 				if (l != NULL) {
-					pidgin_conv_present_conversation((GaimConversation *)l->data);
+					gaim_conversation_present((GaimConversation *)l->data);
 					g_list_free(l);
 				}
 			} else {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/pidgin.pc.in	Tue Feb 13 07:03:51 2007 +0000
@@ -0,0 +1,13 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+datadir=@datadir@
+sysconfdir=@sysconfdir@
+ 
+Name: Pidgin
+Description: Pidgin is a GTK2-based instant messenger application.
+Version: @VERSION@
+Requires: glib-2.0 gtk+-2.0 purple
+Cflags: -I${includedir}/pidgin
+
--- a/pidgin/win32/gtkwin32dep.c	Tue Feb 13 05:00:49 2007 +0000
+++ b/pidgin/win32/gtkwin32dep.c	Tue Feb 13 07:03:51 2007 +0000
@@ -49,6 +49,7 @@
 #include "gtkwin32dep.h"
 #include "win32dep.h"
 #include "gtkconv.h"
+#include "util.h"
 #include "wspell.h"
 
 /*
@@ -192,14 +193,20 @@
 	winpidgin_shell_execute(uri, "open", "http");
 }
 
-#define WM_FOCUS_REQUEST (WM_APP + 13)
+#define PIDGIN_WM_FOCUS_REQUEST (WM_APP + 13)
+#define PIDGIN_WM_PROTOCOL_HANDLE (WM_APP + 14)
 
 static LRESULT CALLBACK message_window_handler(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
 
-	if (msg == WM_FOCUS_REQUEST) {
+	if (msg == PIDGIN_WM_FOCUS_REQUEST) {
 		gaim_debug_info("winpidgin", "Got external Buddy List focus request.");
 		gaim_blist_set_visible(TRUE);
 		return TRUE;
+	} else if (msg == PIDGIN_WM_PROTOCOL_HANDLE) {
+		char *proto_msg = (char *) lparam;
+		gaim_debug_info("winpidgin", "Got protocol handler request: %s\n", proto_msg ? proto_msg : "");
+		gaim_got_protocol_handler_uri(proto_msg);
+		return TRUE;
 	}
 
 	return DefWindowProc(hwnd, msg, wparam, lparam);
--- a/pidgin/win32/pidgin_dll_rc.rc.in	Tue Feb 13 05:00:49 2007 +0000
+++ b/pidgin/win32/pidgin_dll_rc.rc.in	Tue Feb 13 07:03:51 2007 +0000
@@ -14,13 +14,13 @@
     BEGIN
       BLOCK "040904B0"
       BEGIN
-        VALUE "CompanyName", "The Gaim developer community"
-        VALUE "FileDescription", "GTK+ Gaim Library"
+        VALUE "CompanyName", "The Pidgin developer community"
+        VALUE "FileDescription", "GTK+ Pidgin Library"
         VALUE "FileVersion", "@GAIM_VERSION@"
-        VALUE "InternalName", "gtkgaim"
-        VALUE "LegalCopyright", "Copyright (C) 1998-2006 The Gaim developer community (See the COPYRIGHT file in the source distribution)."
-        VALUE "OriginalFilename", "gtkgaim.dll"
-        VALUE "ProductName", "Gaim"
+        VALUE "InternalName", "libpidgin"
+        VALUE "LegalCopyright", "Copyright (C) 1998-2007 The Pidgin developer community (See the COPYRIGHT file in the source distribution)."
+        VALUE "OriginalFilename", "pidgin.dll"
+        VALUE "ProductName", "Pidgin"
         VALUE "ProductVersion", "@GAIM_VERSION@"
       END
     END
--- a/pidgin/win32/pidgin_exe_rc.rc.in	Tue Feb 13 05:00:49 2007 +0000
+++ b/pidgin/win32/pidgin_exe_rc.rc.in	Tue Feb 13 07:03:51 2007 +0000
@@ -17,13 +17,13 @@
     BEGIN
       BLOCK "040904B0"
       BEGIN
-        VALUE "CompanyName", "The Gaim developer community"
-        VALUE "FileDescription", "Gaim"
+        VALUE "CompanyName", "The Pidgin developer community"
+        VALUE "FileDescription", "Pidgin"
         VALUE "FileVersion", "@GAIM_VERSION@"
-        VALUE "InternalName", "gaim"
-        VALUE "LegalCopyright", "Copyright (C) 1998-2006 The Gaim developer community (See the COPYRIGHT file in the source distribution)."
+        VALUE "InternalName", "pidgin"
+        VALUE "LegalCopyright", "Copyright (C) 1998-2007 The Pidgin developer community (See the COPYRIGHT file in the source distribution)."
         VALUE "OriginalFilename", "@ORIGINAL_FILENAME@"
-        VALUE "ProductName", "Gaim"
+        VALUE "ProductName", "Pidgin"
         VALUE "ProductVersion", "@GAIM_VERSION@"
       END
     END
--- a/pidgin/win32/winpidgin.c	Tue Feb 13 05:00:49 2007 +0000
+++ b/pidgin/win32/winpidgin.c	Tue Feb 13 07:03:51 2007 +0000
@@ -433,7 +433,8 @@
 	putenv(envstr);
 }
 
-#define WM_FOCUS_REQUEST (WM_APP + 13)
+#define PIDGIN_WM_FOCUS_REQUEST (WM_APP + 13)
+#define PIDGIN_WM_PROTOCOL_HANDLE (WM_APP + 14)
 
 static BOOL winpidgin_set_running() {
 	HANDLE h;
@@ -443,7 +444,7 @@
 			HWND msg_win;
 
 			if((msg_win = FindWindow(TEXT("WinpidginMsgWinCls"), NULL)))
-				if(SendMessage(msg_win, WM_FOCUS_REQUEST, (WPARAM) NULL, (LPARAM) NULL))
+				if(SendMessage(msg_win, PIDGIN_WM_FOCUS_REQUEST, (WPARAM) NULL, (LPARAM) NULL))
 					return FALSE;
 
 			/* If we get here, the focus request wasn't successful */
@@ -458,12 +459,67 @@
 	return TRUE;
 }
 
+#define PROTO_HANDLER_SWITCH "--protocolhandler="
 
-#ifdef __GNUC__
-#  ifndef _stdcall
-#    define _stdcall  __attribute__((stdcall))
-#  endif
-#endif
+static void handle_protocol(char *cmd) {
+	char *remote_msg, *tmp1, *tmp2;
+	int len;
+	SIZE_T len_written;
+	HWND msg_win;
+	DWORD pid;
+	HANDLE process;
+
+	/* The start of the message */
+	tmp1 = cmd + strlen(PROTO_HANDLER_SWITCH);
+
+	/* The end of the message */
+	if ((tmp2 = strchr(tmp1, ' ')))
+		len = (tmp2 - tmp1);
+	else
+		len = strlen(tmp1);
+
+	if (len == 0) {
+		printf("No protocol message specified.\n");
+		return;
+	}
+
+	if (!(msg_win = FindWindow(TEXT("WinpidginMsgWinCls"), NULL))) {
+		printf("Unable to find an instance of Pidgin to handle protocol message.\n");
+		return;
+	}
+
+	GetWindowThreadProcessId(msg_win, &pid);
+	if (!(process = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, pid))) {
+		DWORD dw = GetLastError();
+		const char *err_msg = get_win32_error_message(dw);
+		printf("Unable to open Pidgin process. (%u) %s\n", (UINT) dw, err_msg);
+		return;
+	}
+
+	printf("Trying to handle protocol message:\n'%*s'\n", len, tmp1);
+
+	/* MEM_COMMIT initializes the memory to zero,
+	 * so we don't need to worry that our section of tmp1 isn't nul-terminated */
+	if ((remote_msg = (char*) VirtualAllocEx(process, NULL, len + 1, MEM_COMMIT, PAGE_READWRITE))) {
+		if (WriteProcessMemory(process, remote_msg, tmp1, len, &len_written)) {
+			if (!SendMessage(msg_win, PIDGIN_WM_PROTOCOL_HANDLE, len_written, (LPARAM) remote_msg))
+				printf("Unable to send protocol message to Pidgin instance.\n");
+		} else {
+			DWORD dw = GetLastError();
+			const char *err_msg = get_win32_error_message(dw);
+			printf("Unable to write to remote memory. (%u) %s\n", (UINT) dw, err_msg);
+		}
+
+		VirtualFreeEx(process, remote_msg, 0, MEM_RELEASE);
+	} else {
+		DWORD dw = GetLastError();
+		const char *err_msg = get_win32_error_message(dw);
+		printf("Unable to allocate remote memory. (%u) %s\n", (UINT) dw, err_msg);
+	}
+
+	CloseHandle(process);
+}
+
 
 int _stdcall
 WinMain (struct HINSTANCE__ *hInstance, struct HINSTANCE__ *hPrevInstance,
@@ -471,6 +527,7 @@
 	char errbuf[512];
 	char pidgindir[MAX_PATH];
 	HMODULE hmod;
+	char *tmp;
 
 	/* If debug or help or version flag used, create console for output */
 	if (strstr(lpszCmdLine, "-d") || strstr(lpszCmdLine, "-h") || strstr(lpszCmdLine, "-v")) {
@@ -491,10 +548,16 @@
 		}
 	}
 
+	/* If this is a protocol handler invocation, deal with it accordingly */
+	if ((tmp = strstr(lpszCmdLine, PROTO_HANDLER_SWITCH)) != NULL) {
+		handle_protocol(tmp);
+		return 0;
+	}
+
 	/* Load exception handler if we have it */
 	if (GetModuleFileName(NULL, pidgindir, MAX_PATH) != 0) {
-		char *tmp = pidgindir;
 		char *prev = NULL;
+		tmp = pidgindir;
 
 		while ((tmp = strchr(tmp, '\\'))) {
 			prev = tmp;