summaryrefslogtreecommitdiff
path: root/libpurple/plugins
diff options
context:
space:
mode:
authorSean Egan <seanegan@pidgin.im>2007-01-20 02:32:10 +0000
committerSean Egan <seanegan@pidgin.im>2007-01-20 02:32:10 +0000
commit87b298a83b8bca7be01ddc853ea4cd15e455b44e (patch)
tree0fe4490ce85d4f60342ad371f49a5df37ae0aeda /libpurple/plugins
parent0979b409b87b353266d00be787749486024a840a (diff)
downloadpidgin-87b298a83b8bca7be01ddc853ea4cd15e455b44e.tar.gz
Rename gtk/ and libgaim/ to pidgin/ and libpurple/
Diffstat (limited to 'libpurple/plugins')
-rw-r--r--libpurple/plugins/Makefile.am129
-rw-r--r--libpurple/plugins/Makefile.mingw82
-rw-r--r--libpurple/plugins/autoaccept.c273
-rw-r--r--libpurple/plugins/autoreply.c435
-rw-r--r--libpurple/plugins/buddynote.c110
-rw-r--r--libpurple/plugins/ciphertest.c287
-rwxr-xr-xlibpurple/plugins/codeinline.c93
-rw-r--r--libpurple/plugins/dbus-buddyicons-example.py36
-rw-r--r--libpurple/plugins/dbus-example.c178
-rw-r--r--libpurple/plugins/filectl.c270
-rw-r--r--libpurple/plugins/fortuneprofile.pl125
-rw-r--r--libpurple/plugins/gaim.pl39
-rw-r--r--libpurple/plugins/idle.c337
-rw-r--r--libpurple/plugins/ipc-test-client.c114
-rw-r--r--libpurple/plugins/ipc-test-server.c99
-rw-r--r--libpurple/plugins/log_reader.c2151
-rw-r--r--libpurple/plugins/mono/BooPlugin.boo22
-rw-r--r--libpurple/plugins/mono/GetBuddyBack.cs32
-rw-r--r--libpurple/plugins/mono/MPlugin.cs36
-rw-r--r--libpurple/plugins/mono/Makefile.am19
-rw-r--r--libpurple/plugins/mono/api/BlistNode.cs4
-rw-r--r--libpurple/plugins/mono/api/Buddy.cs9
-rw-r--r--libpurple/plugins/mono/api/BuddyList.cs19
-rw-r--r--libpurple/plugins/mono/api/Contact.cs4
-rw-r--r--libpurple/plugins/mono/api/Debug.cs28
-rw-r--r--libpurple/plugins/mono/api/Event.cs21
-rw-r--r--libpurple/plugins/mono/api/GaimPlugin.cs61
-rw-r--r--libpurple/plugins/mono/api/Group.cs4
-rw-r--r--libpurple/plugins/mono/api/Makefile.am26
-rw-r--r--libpurple/plugins/mono/api/Signal.cs18
-rw-r--r--libpurple/plugins/mono/api/Status.cs9
-rw-r--r--libpurple/plugins/mono/loader/Makefile.am25
-rw-r--r--libpurple/plugins/mono/loader/blist-glue.c26
-rw-r--r--libpurple/plugins/mono/loader/debug-glue.c16
-rw-r--r--libpurple/plugins/mono/loader/mono-glue.h19
-rw-r--r--libpurple/plugins/mono/loader/mono-helper.c251
-rw-r--r--libpurple/plugins/mono/loader/mono-helper.h73
-rw-r--r--libpurple/plugins/mono/loader/mono.c235
-rw-r--r--libpurple/plugins/mono/loader/signal-glue.c139
-rw-r--r--libpurple/plugins/mono/loader/status-glue.c16
-rw-r--r--libpurple/plugins/newline.c91
-rw-r--r--libpurple/plugins/offlinemsg.c239
-rw-r--r--libpurple/plugins/perl/Makefile.am168
-rwxr-xr-xlibpurple/plugins/perl/Makefile.mingw82
-rw-r--r--libpurple/plugins/perl/common/Account.xs326
-rw-r--r--libpurple/plugins/perl/common/AccountOpts.xs168
-rw-r--r--libpurple/plugins/perl/common/BuddyIcon.xs90
-rw-r--r--libpurple/plugins/perl/common/BuddyList.xs383
-rw-r--r--libpurple/plugins/perl/common/Cipher.xs157
-rw-r--r--libpurple/plugins/perl/common/Cmds.xs49
-rw-r--r--libpurple/plugins/perl/common/Connection.xs89
-rw-r--r--libpurple/plugins/perl/common/Conversation.xs402
-rw-r--r--libpurple/plugins/perl/common/Core.xs28
-rw-r--r--libpurple/plugins/perl/common/Debug.xs54
-rw-r--r--libpurple/plugins/perl/common/FT.xs168
-rw-r--r--libpurple/plugins/perl/common/Gaim.pm131
-rw-r--r--libpurple/plugins/perl/common/Gaim.xs99
-rw-r--r--libpurple/plugins/perl/common/ImgStore.xs35
-rw-r--r--libpurple/plugins/perl/common/Log.xs94
-rw-r--r--libpurple/plugins/perl/common/MANIFEST37
-rw-r--r--libpurple/plugins/perl/common/Makefile.PL.in39
-rw-r--r--libpurple/plugins/perl/common/Makefile.mingw117
-rw-r--r--libpurple/plugins/perl/common/Network.xs45
-rw-r--r--libpurple/plugins/perl/common/Notify.xs138
-rw-r--r--libpurple/plugins/perl/common/Plugin.xs156
-rw-r--r--libpurple/plugins/perl/common/PluginPref.xs144
-rw-r--r--libpurple/plugins/perl/common/Pounce.xs90
-rw-r--r--libpurple/plugins/perl/common/Prefs.xs151
-rw-r--r--libpurple/plugins/perl/common/Privacy.xs43
-rw-r--r--libpurple/plugins/perl/common/Proxy.xs65
-rw-r--r--libpurple/plugins/perl/common/Prpl.xs54
-rw-r--r--libpurple/plugins/perl/common/Request.xs583
-rw-r--r--libpurple/plugins/perl/common/Roomlist.xs84
-rw-r--r--libpurple/plugins/perl/common/SSLConn.xs61
-rw-r--r--libpurple/plugins/perl/common/SavedStatuses.xs58
-rw-r--r--libpurple/plugins/perl/common/Server.xs216
-rw-r--r--libpurple/plugins/perl/common/Signal.xs34
-rw-r--r--libpurple/plugins/perl/common/Sound.xs27
-rw-r--r--libpurple/plugins/perl/common/Status.xs439
-rw-r--r--libpurple/plugins/perl/common/Stringref.xs37
-rw-r--r--libpurple/plugins/perl/common/Util.xs267
-rw-r--r--libpurple/plugins/perl/common/XMLNode.xs88
-rw-r--r--libpurple/plugins/perl/common/fallback/const-c.inc115
-rw-r--r--libpurple/plugins/perl/common/fallback/const-xs.inc88
-rw-r--r--libpurple/plugins/perl/common/module.h278
-rw-r--r--libpurple/plugins/perl/common/typemap209
-rw-r--r--libpurple/plugins/perl/libgaimperl.c9
-rw-r--r--libpurple/plugins/perl/perl-common.c618
-rw-r--r--libpurple/plugins/perl/perl-common.h61
-rw-r--r--libpurple/plugins/perl/perl-handlers.c636
-rw-r--r--libpurple/plugins/perl/perl-handlers.h71
-rw-r--r--libpurple/plugins/perl/perl.c611
-rw-r--r--libpurple/plugins/perl/scripts/account.pl122
-rw-r--r--libpurple/plugins/perl/scripts/buddy_list.pl107
-rw-r--r--libpurple/plugins/perl/scripts/conversation.pl119
-rw-r--r--libpurple/plugins/perl/scripts/count_down.pl89
-rw-r--r--libpurple/plugins/perl/scripts/gtk_frame_test.pl66
-rw-r--r--libpurple/plugins/perl/scripts/plugin_action.pl58
-rw-r--r--libpurple/plugins/perl/scripts/plugin_pref.pl94
-rw-r--r--libpurple/plugins/perl/scripts/request.pl109
-rw-r--r--libpurple/plugins/pluginpref_example.c162
-rw-r--r--libpurple/plugins/psychic.c164
-rw-r--r--libpurple/plugins/signals-test.c708
-rw-r--r--libpurple/plugins/simple.c61
-rw-r--r--libpurple/plugins/ssl/Makefile.am35
-rw-r--r--libpurple/plugins/ssl/Makefile.mingw95
-rw-r--r--libpurple/plugins/ssl/ssl-gnutls.c264
-rw-r--r--libpurple/plugins/ssl/ssl-nss.c433
-rw-r--r--libpurple/plugins/ssl/ssl.c118
-rw-r--r--libpurple/plugins/statenotify.c172
-rw-r--r--libpurple/plugins/tcl/Makefile.am22
-rw-r--r--libpurple/plugins/tcl/Makefile.mingw77
-rw-r--r--libpurple/plugins/tcl/signal-test.tcl110
-rw-r--r--libpurple/plugins/tcl/tcl.c527
-rw-r--r--libpurple/plugins/tcl/tcl_cmd.c190
-rw-r--r--libpurple/plugins/tcl/tcl_cmds.c1607
-rw-r--r--libpurple/plugins/tcl/tcl_gaim.h116
-rw-r--r--libpurple/plugins/tcl/tcl_glib.c254
-rw-r--r--libpurple/plugins/tcl/tcl_glib.h29
-rw-r--r--libpurple/plugins/tcl/tcl_ref.c152
-rw-r--r--libpurple/plugins/tcl/tcl_signals.c396
-rw-r--r--libpurple/plugins/test.pl58
122 files changed, 20906 insertions, 0 deletions
diff --git a/libpurple/plugins/Makefile.am b/libpurple/plugins/Makefile.am
new file mode 100644
index 0000000000..26324007af
--- /dev/null
+++ b/libpurple/plugins/Makefile.am
@@ -0,0 +1,129 @@
+DIST_SUBDIRS = mono perl ssl tcl
+
+if USE_PERL
+PERL_DIR = perl
+endif
+
+if USE_TCL
+TCL_DIR = tcl
+endif
+
+if ENABLE_DBUS
+DBUS_LTLIB = dbus-example.la
+endif
+
+if USE_MONO
+MONO_DIR = mono
+endif
+
+SUBDIRS = \
+ $(MONO_DIR) \
+ $(PERL_DIR) \
+ ssl \
+ $(TCL_DIR)
+
+plugindir = $(libdir)/gaim
+
+autoaccept_la_LDFLAGS = -module -avoid-version
+autoreply_la_LDFLAGS = -module -avoid-version
+buddynote_la_LDFLAGS = -module -avoid-version
+idle_la_LDFLAGS = -module -avoid-version
+log_reader_la_LDFLAGS = -module -avoid-version
+newline_la_LDFLAGS = -module -avoid-version
+offlinemsg_la_LDFLAGS = -module -avoid-version
+psychic_la_LDFLAGS = -module -avoid-version
+statenotify_la_LDFLAGS = -module -avoid-version
+
+# this can't be in a conditional otherwise automake 1.4 yells
+dbus_example_la_LDFLAGS = -module -avoid-version
+
+if PLUGINS
+
+plugin_LTLIBRARIES = \
+ autoaccept.la \
+ autoreply.la \
+ buddynote.la \
+ idle.la \
+ log_reader.la \
+ newline.la \
+ offlinemsg.la \
+ psychic.la \
+ statenotify.la \
+ $(DBUS_LTLIB)
+
+autoaccept_la_SOURCES = autoaccept.c
+autoreply_la_SOURCES = autoreply.c
+buddynote_la_SOURCES = buddynote.c
+idle_la_SOURCES = idle.c
+log_reader_la_SOURCES = log_reader.c
+newline_la_SOURCES = newline.c
+offlinemsg_la_SOURCES = offlinemsg.c
+psychic_la_SOURCES = psychic.c
+statenotify_la_SOURCES = statenotify.c
+
+autoaccept_la_LIBADD = $(GLIB_LIBS)
+autoreply_la_LIBADD = $(GLIB_LIBS)
+buddynote_la_LIBADD = $(GLIB_LIBS)
+idle_la_LIBADD = $(GLIB_LIBS)
+log_reader_la_LIBADD = $(GLIB_LIBS)
+newline_la_LIBADD = $(GLIB_LIBS)
+offlinemsg_la_LIBADD = $(GLIB_LIBS)
+psychic_la_LIBADD = $(GLIB_LIBS)
+statenotify_la_LIBADD = $(GLIB_LIBS)
+
+if ENABLE_DBUS
+
+CLEANFILES = dbus-example-bindings.c
+dbus_example_la_SOURCES = dbus-example.c
+
+dbus_example_la_LIBADD = $(GLIB_LIBS) $(DBUS_LIBS)
+
+.PHONY: always
+
+$(top_builddir)/libpurple/dbus-types.h: always
+ cd $(@D) && $(MAKE) $(AM_MAKEFLAGS) $(@F)
+
+dbus-example-bindings.c: $(top_srcdir)/libpurple/dbus-analyze-functions.py $(dbus_example_la_SOURCES)
+ cat $(srcdir)/$(dbus_example_la_SOURCES) | \
+ $(PYTHON) $(top_srcdir)/libpurple/dbus-analyze-functions.py --export-only > $@
+
+$(dbus_example_la_OBJECTS) dbus-example.so: dbus-example-bindings.c $(top_builddir)/libpurple/dbus-types.h
+
+
+endif # ENABLE_DBUS
+
+endif # PLUGINS
+
+EXTRA_DIST = \
+ Makefile.mingw \
+ dbus-buddyicons-example.py \
+ filectl.c \
+ fortuneprofile.pl \
+ gaim.pl \
+ ipc-test-client.c \
+ ipc-test-server.c \
+ pluginpref_example.c \
+ signals-test.c \
+ simple.c
+
+AM_CPPFLAGS = \
+ -DDATADIR=\"$(datadir)\" \
+ -DVERSION=\"$(VERSION)\" \
+ -I$(top_builddir)/libpurple \
+ -I$(top_srcdir)/libpurple \
+ $(DEBUG_CFLAGS) \
+ $(GLIB_CFLAGS) \
+ $(PLUGIN_CFLAGS) \
+ $(DBUS_CFLAGS)
+
+#
+# This part allows people to build their own plugins in here.
+# Yes, it's a mess.
+#
+SUFFIXES = .c .so
+.c.so:
+ $(LIBTOOL) --mode=compile $(CC) -DHAVE_CONFIG_H -I$(top_srcdir) $(AM_CPPFLAGS) $(CFLAGS) -c $< -o tmp$@.lo $(PLUGIN_CFLAGS)
+ $(LIBTOOL) --mode=link $(CC) $(CFLAGS) -o libtmp$@.la -rpath $(plugindir) tmp$@.lo $(LIBS) $(LDFLAGS) -module -avoid-version $(PLUGIN_LIBS)
+ @rm -f tmp$@.lo tmp$@.o libtmp$@.la
+ @cp .libs/libtmp$@.so* $@
+ @rm -f .libs/libtmp$@.*
diff --git a/libpurple/plugins/Makefile.mingw b/libpurple/plugins/Makefile.mingw
new file mode 100644
index 0000000000..4076c06c54
--- /dev/null
+++ b/libpurple/plugins/Makefile.mingw
@@ -0,0 +1,82 @@
+#
+# Makefile.mingw
+#
+# Description: Makefile for win32 (mingw) version of Gaim Plugins
+#
+
+GAIM_TOP := ../..
+include $(GAIM_TOP)/libgaim/win32/global.mak
+
+PERL_PLUGIN := ./perl
+TCL_PLUGIN := ./tcl
+SSL_PLUGIN := ./ssl
+
+.SUFFIXES:
+.SUFFIXES: .c .dll
+
+##
+## INCLUDE PATHS
+##
+INCLUDE_PATHS += \
+ -I$(GTK_TOP)/include \
+ -I$(GTK_TOP)/include/glib-2.0 \
+ -I$(GTK_TOP)/lib/glib-2.0/include \
+ -I$(GAIM_TOP) \
+ -I$(GAIM_LIB_TOP) \
+ -I$(GAIM_LIB_TOP)/win32
+
+LIB_PATHS = -L$(GTK_TOP)/lib \
+ -L$(GAIM_LIB_TOP)
+
+##
+## LIBRARIES
+##
+LIBS = \
+ -lglib-2.0 \
+ -lgobject-2.0 \
+ -lgmodule-2.0 \
+ -lintl \
+ -lws2_32 \
+ -lgaim
+
+##
+## TARGET DEFINITIONS
+##
+.PHONY: all clean plugins install
+
+all: $(GAIM_LIBGAIM_DLL).a plugins
+ $(MAKE) -C $(PERL_PLUGIN) -f $(GAIM_WIN32_MAKEFILE)
+ $(MAKE) -C $(TCL_PLUGIN) -f $(GAIM_WIN32_MAKEFILE)
+ $(MAKE) -C $(SSL_PLUGIN) -f $(GAIM_WIN32_MAKEFILE)
+
+install: all $(GAIM_INSTALL_PLUGINS_DIR)
+ $(MAKE) -C $(PERL_PLUGIN) -f $(GAIM_WIN32_MAKEFILE) install
+ $(MAKE) -C $(TCL_PLUGIN) -f $(GAIM_WIN32_MAKEFILE) install
+ $(MAKE) -C $(SSL_PLUGIN) -f $(GAIM_WIN32_MAKEFILE) install
+ cp *.dll $(GAIM_INSTALL_PLUGINS_DIR)
+
+.c.dll:
+ $(CC) $(CFLAGS) $(DEFINES) $(INCLUDE_PATHS) -o $@.o -c $<
+ $(CC) -shared $@.o $(LIB_PATHS) $(LIBS) $(DLL_LD_FLAGS) -o $@
+
+plugins: \
+ autoaccept.dll \
+ autoreply.dll \
+ buddynote.dll \
+ idle.dll \
+ log_reader.dll \
+ newline.dll \
+ offlinemsg.dll \
+ psychic.dll \
+ statenotify.dll
+
+##
+## CLEAN RULES
+##
+clean:
+ rm -f *.o *.dll
+ $(MAKE) -C $(PERL_PLUGIN) -f $(GAIM_WIN32_MAKEFILE) clean
+ $(MAKE) -C $(TCL_PLUGIN) -f $(GAIM_WIN32_MAKEFILE) clean
+ $(MAKE) -C $(SSL_PLUGIN) -f $(GAIM_WIN32_MAKEFILE) clean
+
+include $(GAIM_COMMON_TARGETS)
diff --git a/libpurple/plugins/autoaccept.c b/libpurple/plugins/autoaccept.c
new file mode 100644
index 0000000000..cd4185f126
--- /dev/null
+++ b/libpurple/plugins/autoaccept.c
@@ -0,0 +1,273 @@
+/*
+ * Autoaccept - Auto-accept file transfers from selected users
+ * Copyright (C) 2006
+ *
+ * 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"
+
+#define PLUGIN_ID "core-plugin_pack-autoaccept"
+#define PLUGIN_NAME N_("Autoaccept")
+#define PLUGIN_STATIC_NAME "Autoaccept"
+#define PLUGIN_SUMMARY N_("Auto-accept file transfer requests from selected users.")
+#define PLUGIN_DESCRIPTION N_("Auto-accept file transfer requests from selected users.")
+#define PLUGIN_AUTHOR "Sadrul H Chowdhury <sadrul@users.sourceforge.net>"
+
+/* System headers */
+#include <glib.h>
+#if GLIB_CHECK_VERSION(2,6,0)
+# include <glib/gstdio.h>
+#else
+# include <sys/types.h>
+# include <sys/stat.h>
+# define g_mkdir mkdir
+#endif
+
+/* Gaim headers */
+#include <plugin.h>
+#include <version.h>
+
+#include <blist.h>
+#include <conversation.h>
+#include <ft.h>
+#include <request.h>
+#include <notify.h>
+#include <util.h>
+
+#define PREF_PREFIX "/plugins/core/" PLUGIN_ID
+#define PREF_PATH PREF_PREFIX "/path"
+#define PREF_STRANGER PREF_PREFIX "/reject_stranger"
+#define PREF_NOTIFY PREF_PREFIX "/notify"
+
+typedef enum
+{
+ FT_ASK,
+ FT_ACCEPT,
+ FT_REJECT
+} AutoAcceptSetting;
+
+static gboolean
+ensure_path_exists(const char *dir)
+{
+ if (!g_file_test(dir, G_FILE_TEST_IS_DIR))
+ {
+ if (gaim_build_dir(dir, S_IRUSR | S_IWUSR | S_IXUSR))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+auto_accept_complete_cb(GaimXfer *xfer, GaimXfer *my)
+{
+ if (xfer == my && gaim_prefs_get_bool(PREF_NOTIFY) &&
+ !gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM, xfer->who, xfer->account))
+ {
+ char *message = g_strdup_printf(_("Autoaccepted file transfer of \"%s\" from \"%s\" completed."),
+ xfer->filename, xfer->who);
+ gaim_notify_info(NULL, _("Autoaccept complete"), message, NULL);
+ g_free(message);
+ }
+}
+
+static void
+file_recv_request_cb(GaimXfer *xfer, gpointer handle)
+{
+ GaimAccount *account;
+ GaimBlistNode *node;
+ const char *pref;
+ char *filename;
+ char *dirname;
+
+ account = xfer->account;
+ node = (GaimBlistNode *)gaim_find_buddy(account, xfer->who);
+
+ if (!node)
+ {
+ if (gaim_prefs_get_bool(PREF_STRANGER))
+ xfer->status = GAIM_XFER_STATUS_CANCEL_LOCAL;
+ return;
+ }
+
+ node = node->parent;
+ g_return_if_fail(GAIM_BLIST_NODE_IS_CONTACT(node));
+
+ pref = gaim_prefs_get_string(PREF_PATH);
+ switch (gaim_blist_node_get_int(node, "autoaccept"))
+ {
+ case FT_ASK:
+ break;
+ case FT_ACCEPT:
+ if (ensure_path_exists(pref))
+ {
+ dirname = g_build_filename(pref, xfer->who, NULL);
+
+ if (!ensure_path_exists(dirname))
+ {
+ g_free(dirname);
+ break;
+ }
+
+ filename = g_build_filename(dirname, xfer->filename, NULL);
+
+ gaim_xfer_request_accepted(xfer, filename);
+
+ g_free(dirname);
+ g_free(filename);
+ }
+
+ gaim_signal_connect(gaim_xfers_get_handle(), "file-recv-complete", handle,
+ GAIM_CALLBACK(auto_accept_complete_cb), xfer);
+ break;
+ case FT_REJECT:
+ xfer->status = GAIM_XFER_STATUS_CANCEL_LOCAL;
+ break;
+ }
+}
+
+static void
+save_cb(GaimBlistNode *node, int choice)
+{
+ if (GAIM_BLIST_NODE_IS_BUDDY(node))
+ node = node->parent;
+ g_return_if_fail(GAIM_BLIST_NODE_IS_CONTACT(node));
+ gaim_blist_node_set_int(node, "autoaccept", choice);
+}
+
+static void
+set_auto_accept_settings(GaimBlistNode *node, gpointer plugin)
+{
+ char *message;
+
+ if (GAIM_BLIST_NODE_IS_BUDDY(node))
+ node = node->parent;
+ g_return_if_fail(GAIM_BLIST_NODE_IS_CONTACT(node));
+
+ message = g_strdup_printf(_("When a file-transfer request arrives from %s"),
+ gaim_contact_get_alias((GaimContact *)node));
+ gaim_request_choice(plugin, _("Set Autoaccept Setting"), message,
+ NULL, gaim_blist_node_get_int(node, "autoaccept"),
+ _("_Save"), G_CALLBACK(save_cb),
+ _("_Cancel"), NULL, node,
+ _("Ask"), FT_ASK,
+ _("Auto Accept"), FT_ACCEPT,
+ _("Auto Reject"), FT_REJECT,
+ NULL);
+ g_free(message);
+}
+
+static void
+context_menu(GaimBlistNode *node, GList **menu, gpointer plugin)
+{
+ GaimMenuAction *action;
+
+ if (!GAIM_BLIST_NODE_IS_BUDDY(node) && !GAIM_BLIST_NODE_IS_CONTACT(node))
+ return;
+
+ action = gaim_menu_action_new(_("Autoaccept File Transfers..."),
+ GAIM_CALLBACK(set_auto_accept_settings), plugin, NULL);
+ (*menu) = g_list_prepend(*menu, action);
+}
+
+static gboolean
+plugin_load(GaimPlugin *plugin)
+{
+ gaim_signal_connect(gaim_xfers_get_handle(), "file-recv-request", plugin,
+ GAIM_CALLBACK(file_recv_request_cb), plugin);
+ gaim_signal_connect(gaim_blist_get_handle(), "blist-node-extended-menu", plugin,
+ GAIM_CALLBACK(context_menu), plugin);
+ return TRUE;
+}
+
+static gboolean
+plugin_unload(GaimPlugin *plugin)
+{
+ return TRUE;
+}
+
+static GaimPluginPrefFrame *
+get_plugin_pref_frame(GaimPlugin *plugin)
+{
+ GaimPluginPrefFrame *frame;
+ GaimPluginPref *pref;
+
+ frame = gaim_plugin_pref_frame_new();
+
+ /* XXX: Is there a better way than this? There really should be. */
+ pref = gaim_plugin_pref_new_with_name_and_label(PREF_PATH, _("Path to save the files in\n"
+ "(Please provide the full path)"));
+ gaim_plugin_pref_frame_add(frame, pref);
+
+ pref = gaim_plugin_pref_new_with_name_and_label(PREF_STRANGER,
+ _("Automatically reject from users not in buddy list"));
+ gaim_plugin_pref_frame_add(frame, pref);
+
+ pref = gaim_plugin_pref_new_with_name_and_label(PREF_NOTIFY,
+ _("Notify with a popup when an autoaccepted file transfer is complete\n"
+ "(only when there's no conversation with the sender)"));
+ gaim_plugin_pref_frame_add(frame, pref);
+
+ return frame;
+}
+
+static GaimPluginUiInfo prefs_info = {
+ get_plugin_pref_frame,
+ 0,
+ NULL,
+};
+
+static GaimPluginInfo info = {
+ GAIM_PLUGIN_MAGIC, /* Magic */
+ GAIM_MAJOR_VERSION, /* Gaim Major Version */
+ GAIM_MINOR_VERSION, /* Gaim Minor Version */
+ GAIM_PLUGIN_STANDARD, /* plugin type */
+ NULL, /* ui requirement */
+ 0, /* flags */
+ NULL, /* dependencies */
+ GAIM_PRIORITY_DEFAULT, /* priority */
+
+ PLUGIN_ID, /* plugin id */
+ PLUGIN_NAME, /* name */
+ VERSION, /* version */
+ PLUGIN_SUMMARY, /* summary */
+ PLUGIN_DESCRIPTION, /* description */
+ PLUGIN_AUTHOR, /* author */
+ GAIM_WEBSITE, /* website */
+
+ plugin_load, /* load */
+ plugin_unload, /* unload */
+ NULL, /* destroy */
+
+ NULL, /* ui_info */
+ NULL, /* extra_info */
+ &prefs_info, /* prefs_info */
+ NULL /* actions */
+};
+
+static void
+init_plugin(GaimPlugin *plugin) {
+ char *dirname;
+
+ dirname = g_build_filename(gaim_user_dir(), "autoaccept", NULL);
+ gaim_prefs_add_none(PREF_PREFIX);
+ gaim_prefs_add_string(PREF_PATH, dirname);
+ gaim_prefs_add_bool(PREF_STRANGER, TRUE);
+ gaim_prefs_add_bool(PREF_NOTIFY, TRUE);
+ g_free(dirname);
+}
+
+GAIM_INIT_PLUGIN(PLUGIN_STATIC_NAME, init_plugin, info)
diff --git a/libpurple/plugins/autoreply.c b/libpurple/plugins/autoreply.c
new file mode 100644
index 0000000000..df4602247d
--- /dev/null
+++ b/libpurple/plugins/autoreply.c
@@ -0,0 +1,435 @@
+/*
+ * Autoreply - Autoreply feature for all the protocols
+ * Copyright (C) 2005
+ *
+ * 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"
+
+#define PLUGIN_ID "core-plugin_pack-autoreply"
+#define PLUGIN_NAME N_("Autoreply")
+#define PLUGIN_STATIC_NAME "Autoreply"
+#define PLUGIN_SUMMARY N_("Autoreply for all the protocols")
+#define PLUGIN_DESCRIPTION N_("This plugin lets you set autoreply message for any protocol. "\
+ "You can set the global autoreply message from the Plugin-options dialog. " \
+ "To set some specific autoreply message for a particular buddy, right click " \
+ "on the buddy in the buddy-list window. To set autoreply messages for some " \
+ "account, go to the `Advanced' tab of the Account-edit dialog.")
+#define PLUGIN_AUTHOR "Sadrul Habib Chowdhury <sadrul@users.sourceforge.net>"
+
+/* System headers */
+#include <glib.h>
+
+/* Gaim headers */
+#include <account.h>
+#include <accountopt.h>
+#include <blist.h>
+#include <conversation.h>
+#include <plugin.h>
+#include <pluginpref.h>
+#include <request.h>
+#include <savedstatuses.h>
+#include <status.h>
+#include <util.h>
+#include <version.h>
+
+#define PREFS_PREFIX "/core/" PLUGIN_ID
+#define PREFS_IDLE PREFS_PREFIX "/idle"
+#define PREFS_AWAY PREFS_PREFIX "/away"
+#define PREFS_GLOBAL PREFS_PREFIX "/global"
+#define PREFS_MINTIME PREFS_PREFIX "/mintime"
+#define PREFS_MAXSEND PREFS_PREFIX "/maxsend"
+#define PREFS_USESTATUS PREFS_PREFIX "/usestatus"
+
+typedef struct _GaimAutoReply GaimAutoReply;
+
+struct _GaimAutoReply
+{
+ GaimBuddy *buddy;
+ char *reply;
+};
+
+typedef enum
+{
+ STATUS_NEVER,
+ STATUS_ALWAYS,
+ STATUS_FALLBACK
+} UseStatusMessage;
+
+static GHashTable *options = NULL;
+
+/**
+ * Returns the auto-reply message for buddy
+ */
+static const char *
+get_autoreply_message(GaimBuddy *buddy, GaimAccount *account)
+{
+ const char *reply = NULL;
+ UseStatusMessage use_status;
+
+ use_status = gaim_prefs_get_int(PREFS_USESTATUS);
+ if (use_status == STATUS_ALWAYS)
+ {
+ GaimStatus *status = gaim_account_get_active_status(account);
+ GaimStatusType *type = gaim_status_get_type(status);
+ if (gaim_status_type_get_attr(type, "message") != NULL)
+ reply = gaim_status_get_attr_string(status, "message");
+ else
+ reply = gaim_savedstatus_get_message(gaim_savedstatus_get_current());
+ }
+
+ if (!reply && buddy)
+ {
+ /* Is there any special auto-reply for this buddy? */
+ reply = gaim_blist_node_get_string((GaimBlistNode*)buddy, "autoreply");
+
+ if (!reply && GAIM_BLIST_NODE_IS_BUDDY((GaimBlistNode*)buddy))
+ {
+ /* Anything for the contact, then? */
+ reply = gaim_blist_node_get_string(((GaimBlistNode*)buddy)->parent, "autoreply");
+ }
+ }
+
+ if (!reply)
+ {
+ /* Is there any specific auto-reply for this account? */
+ reply = gaim_account_get_string(account, "autoreply", NULL);
+ }
+
+ if (!reply)
+ {
+ /* Get the global auto-reply message */
+ reply = gaim_prefs_get_string(PREFS_GLOBAL);
+ }
+
+ if (*reply == ' ')
+ reply = NULL;
+
+ if (!reply && use_status == STATUS_FALLBACK)
+ reply = gaim_status_get_attr_string(gaim_account_get_active_status(account), "message");
+
+ return reply;
+}
+
+static void
+written_msg(GaimAccount *account, const char *who, const char *message,
+ GaimConversation *conv, GaimMessageFlags flags, gpointer null)
+{
+ GaimBuddy *buddy;
+ GaimPresence *presence;
+ const char *reply = NULL;
+ gboolean trigger = FALSE;
+
+ if (!(flags & GAIM_MESSAGE_RECV))
+ return;
+
+ if (!message || !*message)
+ return;
+
+ /* Do not send an autoreply for an autoreply */
+ if (flags & GAIM_MESSAGE_AUTO_RESP)
+ return;
+
+ g_return_if_fail(gaim_conversation_get_type(conv) == GAIM_CONV_TYPE_IM);
+
+ presence = gaim_account_get_presence(account);
+
+ if (gaim_prefs_get_bool(PREFS_AWAY) && !gaim_presence_is_available(presence))
+ trigger = TRUE;
+ if (gaim_prefs_get_bool(PREFS_IDLE) && gaim_presence_is_idle(presence))
+ trigger = TRUE;
+
+ if (!trigger)
+ return;
+
+ buddy = gaim_find_buddy(account, who);
+ reply = get_autoreply_message(buddy, account);
+
+ if (reply)
+ {
+ GaimConnection *gc;
+ GaimMessageFlags flag = GAIM_MESSAGE_SEND;
+ time_t last_sent, now;
+ int count_sent, maxsend;
+
+ last_sent = GPOINTER_TO_INT(gaim_conversation_get_data(conv, "autoreply_lastsent"));
+ now = time(NULL);
+
+ /* Have we spent enough time after our last autoreply? */
+ if (now - last_sent >= (gaim_prefs_get_int(PREFS_MINTIME)*60))
+ {
+ count_sent = GPOINTER_TO_INT(gaim_conversation_get_data(conv, "autoreply_count"));
+ maxsend = gaim_prefs_get_int(PREFS_MAXSEND);
+
+ /* Have we sent the autoreply enough times? */
+ if (count_sent < maxsend || maxsend == -1)
+ {
+ gaim_conversation_set_data(conv, "autoreply_count", GINT_TO_POINTER(++count_sent));
+ gaim_conversation_set_data(conv, "autoreply_lastsent", GINT_TO_POINTER(now));
+ gc = gaim_account_get_connection(account);
+ if (gc->flags & GAIM_CONNECTION_AUTO_RESP)
+ flag |= GAIM_MESSAGE_AUTO_RESP;
+ serv_send_im(gc, who, reply, flag);
+ gaim_conv_im_write(GAIM_CONV_IM(conv), NULL, reply, flag, time(NULL));
+ }
+ }
+ }
+}
+
+static void
+set_auto_reply_cb(GaimBlistNode *node, char *message)
+{
+ if (!message || !*message)
+ message = " ";
+ gaim_blist_node_set_string(node, "autoreply", message);
+}
+
+static void
+set_auto_reply(GaimBlistNode *node, gpointer plugin)
+{
+ char *message;
+ GaimBuddy *buddy;
+ GaimAccount *account;
+ GaimConnection *gc;
+
+ if (GAIM_BLIST_NODE_IS_BUDDY(node))
+ buddy = (GaimBuddy *)node;
+ else
+ buddy = gaim_contact_get_priority_buddy((GaimContact*)node);
+
+ account = gaim_buddy_get_account(buddy);
+ gc = gaim_account_get_connection(account);
+
+ /* XXX: There should be a way to reset to the default/account-default autoreply */
+
+ message = g_strdup_printf(_("Set autoreply message for %s"),
+ gaim_buddy_get_contact_alias(buddy));
+ gaim_request_input(plugin, _("Set Autoreply Message"), message,
+ _("The following message will be sent to the buddy when "
+ "the buddy sends you a message and autoreply is enabled."),
+ get_autoreply_message(buddy, account), TRUE, FALSE,
+ (gc->flags & GAIM_CONNECTION_HTML) ? "html" : NULL,
+ _("_Save"), G_CALLBACK(set_auto_reply_cb),
+ _("_Cancel"), NULL, node);
+ g_free(message);
+}
+
+static void
+context_menu(GaimBlistNode *node, GList **menu, gpointer plugin)
+{
+ GaimMenuAction *action;
+
+ if (!GAIM_BLIST_NODE_IS_BUDDY(node) && !GAIM_BLIST_NODE_IS_CONTACT(node))
+ return;
+
+ action = gaim_menu_action_new(_("Set _Autoreply Message"),
+ GAIM_CALLBACK(set_auto_reply), plugin, NULL);
+ (*menu) = g_list_prepend(*menu, action);
+}
+
+static void
+add_option_for_protocol(GaimPlugin *plg)
+{
+ GaimPluginProtocolInfo *info = GAIM_PLUGIN_PROTOCOL_INFO(plg);
+ GaimAccountOption *option;
+
+ option = gaim_account_option_string_new(_("Autoreply message"), "autoreply", NULL);
+ info->protocol_options = g_list_append(info->protocol_options, option);
+
+ if (!g_hash_table_lookup(options, plg))
+ g_hash_table_insert(options, plg, option);
+}
+
+static void
+remove_option_for_protocol(GaimPlugin *plg)
+{
+ GaimPluginProtocolInfo *info = GAIM_PLUGIN_PROTOCOL_INFO(plg);
+ GaimAccountOption *option = g_hash_table_lookup(options, plg);
+
+ if (g_list_find(info->protocol_options, option))
+ {
+ info->protocol_options = g_list_remove(info->protocol_options, option);
+ gaim_account_option_destroy(option);
+ g_hash_table_remove(options, plg);
+ }
+}
+
+static void
+plugin_load_cb(GaimPlugin *plugin, gboolean load)
+{
+ if (plugin->info && plugin->info->type == GAIM_PLUGIN_PROTOCOL)
+ {
+ if (load)
+ add_option_for_protocol(plugin);
+ else
+ remove_option_for_protocol(plugin);
+ }
+}
+
+static gboolean
+plugin_load(GaimPlugin *plugin)
+{
+ GList *list;
+
+ gaim_signal_connect(gaim_conversations_get_handle(), "wrote-im-msg", plugin,
+ GAIM_CALLBACK(written_msg), NULL);
+ gaim_signal_connect(gaim_blist_get_handle(), "blist-node-extended-menu", plugin,
+ GAIM_CALLBACK(context_menu), plugin);
+ gaim_signal_connect(gaim_plugins_get_handle(), "plugin-load", plugin,
+ GAIM_CALLBACK(plugin_load_cb), GINT_TO_POINTER(TRUE));
+ gaim_signal_connect(gaim_plugins_get_handle(), "plugin-unload", plugin,
+ GAIM_CALLBACK(plugin_load_cb), GINT_TO_POINTER(FALSE));
+
+ /* Perhaps it's necessary to do this after making sure the prpl-s have been loaded? */
+ options = g_hash_table_new(g_direct_hash, g_direct_equal);
+ list = gaim_plugins_get_protocols();
+ while (list)
+ {
+ add_option_for_protocol(list->data);
+ list = list->next;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+plugin_unload(GaimPlugin *plugin)
+{
+ GList *list;
+
+ if (options == NULL)
+ return TRUE;
+
+ list = gaim_plugins_get_protocols();
+ while (list)
+ {
+ remove_option_for_protocol(list->data);
+ list = list->next;
+ }
+ g_hash_table_destroy(options);
+ options = NULL;
+
+ return TRUE;
+}
+
+static GaimPluginPrefFrame *
+get_plugin_pref_frame(GaimPlugin *plugin)
+{
+ GaimPluginPrefFrame *frame;
+ GaimPluginPref *pref;
+
+ frame = gaim_plugin_pref_frame_new();
+
+ pref = gaim_plugin_pref_new_with_label(_("Send autoreply messages when"));
+ gaim_plugin_pref_frame_add(frame, pref);
+
+ pref = gaim_plugin_pref_new_with_name_and_label(PREFS_AWAY,
+ _("When my account is _away"));
+ gaim_plugin_pref_frame_add(frame, pref);
+
+ pref = gaim_plugin_pref_new_with_name_and_label(PREFS_IDLE,
+ _("When my account is _idle"));
+ gaim_plugin_pref_frame_add(frame, pref);
+
+ pref = gaim_plugin_pref_new_with_name_and_label(PREFS_GLOBAL,
+ _("_Default reply"));
+ gaim_plugin_pref_set_type(pref, GAIM_PLUGIN_PREF_STRING_FORMAT);
+ gaim_plugin_pref_set_format_type(pref,
+ GAIM_STRING_FORMAT_TYPE_MULTILINE | GAIM_STRING_FORMAT_TYPE_HTML);
+ gaim_plugin_pref_frame_add(frame, pref);
+
+ pref = gaim_plugin_pref_new_with_label(_("Status message"));
+ gaim_plugin_pref_frame_add(frame, pref);
+
+ pref = gaim_plugin_pref_new_with_name_and_label(PREFS_USESTATUS,
+ _("Autoreply with status message"));
+ gaim_plugin_pref_set_type(pref, GAIM_PLUGIN_PREF_CHOICE);
+ gaim_plugin_pref_add_choice(pref, _("Never"),
+ GINT_TO_POINTER(STATUS_NEVER));
+ gaim_plugin_pref_add_choice(pref, _("Always when there is a status message"),
+ GINT_TO_POINTER(STATUS_ALWAYS));
+ gaim_plugin_pref_add_choice(pref, _("Only when there's no autoreply message"),
+ GINT_TO_POINTER(STATUS_FALLBACK));
+
+ gaim_plugin_pref_frame_add(frame, pref);
+
+ pref = gaim_plugin_pref_new_with_label(_("Delay between autoreplies"));
+ gaim_plugin_pref_frame_add(frame, pref);
+
+ pref = gaim_plugin_pref_new_with_name_and_label(PREFS_MINTIME,
+ _("_Minimum delay (mins)"));
+ gaim_plugin_pref_set_bounds(pref, 0, 9999);
+ gaim_plugin_pref_frame_add(frame, pref);
+
+ pref = gaim_plugin_pref_new_with_label(_("Times to send autoreplies"));
+ gaim_plugin_pref_frame_add(frame, pref);
+
+ pref = gaim_plugin_pref_new_with_name_and_label(PREFS_MAXSEND,
+ _("Ma_ximum count"));
+ gaim_plugin_pref_set_bounds(pref, 0, 9999);
+ gaim_plugin_pref_frame_add(frame, pref);
+
+ return frame;
+}
+
+static GaimPluginUiInfo prefs_info = {
+ get_plugin_pref_frame,
+ 0,
+ NULL
+};
+
+static GaimPluginInfo info = {
+ GAIM_PLUGIN_MAGIC, /* Magic */
+ GAIM_MAJOR_VERSION, /* Gaim Major Version */
+ GAIM_MINOR_VERSION, /* Gaim Minor Version */
+ GAIM_PLUGIN_STANDARD, /* plugin type */
+ NULL, /* ui requirement */
+ 0, /* flags */
+ NULL, /* dependencies */
+ GAIM_PRIORITY_DEFAULT, /* priority */
+
+ PLUGIN_ID, /* plugin id */
+ PLUGIN_NAME, /* name */
+ VERSION, /* version */
+ PLUGIN_SUMMARY, /* summary */
+ PLUGIN_DESCRIPTION, /* description */
+ PLUGIN_AUTHOR, /* author */
+ GAIM_WEBSITE, /* website */
+
+ plugin_load, /* load */
+ plugin_unload, /* unload */
+ NULL, /* destroy */
+
+ NULL, /* ui_info */
+ NULL, /* extra_info */
+ &prefs_info, /* prefs_info */
+ NULL /* actions */
+};
+
+static void
+init_plugin(GaimPlugin *plugin)
+{
+ gaim_prefs_add_none(PREFS_PREFIX);
+ gaim_prefs_add_bool(PREFS_IDLE, TRUE);
+ gaim_prefs_add_bool(PREFS_AWAY, TRUE);
+ gaim_prefs_add_string(PREFS_GLOBAL, _("I am currently not available. Please leave your message, "
+ "and I will get back to you as soon as possible."));
+ gaim_prefs_add_int(PREFS_MINTIME, 10);
+ gaim_prefs_add_int(PREFS_MAXSEND, 10);
+ gaim_prefs_add_int(PREFS_USESTATUS, STATUS_NEVER);
+}
+
+GAIM_INIT_PLUGIN(PLUGIN_STATIC_NAME, init_plugin, info)
diff --git a/libpurple/plugins/buddynote.c b/libpurple/plugins/buddynote.c
new file mode 100644
index 0000000000..9bfe3f922a
--- /dev/null
+++ b/libpurple/plugins/buddynote.c
@@ -0,0 +1,110 @@
+/*
+ * BuddyNote - Store notes on particular buddies
+ * Copyright (C) 2004 Stu Tomlinson <stu@nosnilmot.com>
+ *
+ * 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 <debug.h>
+#include <notify.h>
+#include <request.h>
+#include <signals.h>
+#include <util.h>
+#include <version.h>
+
+static void
+dont_do_it_cb(GaimBlistNode *node, const char *note)
+{
+}
+
+static void
+do_it_cb(GaimBlistNode *node, const char *note)
+{
+ gaim_blist_node_set_string(node, "notes", note);
+}
+
+static void
+buddynote_edit_cb(GaimBlistNode *node, gpointer data)
+{
+ const char *note;
+
+ note = gaim_blist_node_get_string(node, "notes");
+
+ gaim_request_input(node, _("Notes"),
+ _("Enter your notes below..."),
+ NULL,
+ note, TRUE, FALSE, "html",
+ _("Save"), G_CALLBACK(do_it_cb),
+ _("Cancel"), G_CALLBACK(dont_do_it_cb),
+ node);
+}
+
+static void
+buddynote_extended_menu_cb(GaimBlistNode *node, GList **m)
+{
+ GaimMenuAction *bna = NULL;
+
+ *m = g_list_append(*m, bna);
+ bna = gaim_menu_action_new(_("Edit Notes..."), GAIM_CALLBACK(buddynote_edit_cb), NULL, NULL);
+ *m = g_list_append(*m, bna);
+}
+
+static gboolean
+plugin_load(GaimPlugin *plugin)
+{
+
+ gaim_signal_connect(gaim_blist_get_handle(), "blist-node-extended-menu",
+ plugin, GAIM_CALLBACK(buddynote_extended_menu_cb), NULL);
+
+ return TRUE;
+}
+
+static GaimPluginInfo info =
+{
+ GAIM_PLUGIN_MAGIC,
+ GAIM_MAJOR_VERSION, /**< major version */
+ GAIM_MINOR_VERSION,
+ GAIM_PLUGIN_STANDARD, /**< type */
+ NULL, /**< ui_requirement */
+ 0, /**< flags */
+ NULL, /**< dependencies */
+ GAIM_PRIORITY_DEFAULT, /**< priority */
+
+ "core-plugin_pack-buddynote", /**< id */
+ N_("Buddy Notes"), /**< name */
+ VERSION, /**< version */
+ N_("Store notes on particular buddies."), /** summary */
+ N_("Adds the option to store notes for buddies "
+ "on your buddy list."), /** description */
+ "Stu Tomlinson <stu@nosnilmot.com>", /**< author */
+ GAIM_WEBSITE, /**< homepage */
+
+ plugin_load, /**< load */
+ NULL, /**< unload */
+ NULL, /**< destroy */
+
+ NULL, /**< ui_info */
+ NULL, /**< extra_info */
+ NULL, /**< prefs_info */
+ NULL /**< actions */
+};
+
+
+static void
+init_plugin(GaimPlugin *plugin) {
+}
+
+GAIM_INIT_PLUGIN(buddynote, init_plugin, info)
diff --git a/libpurple/plugins/ciphertest.c b/libpurple/plugins/ciphertest.c
new file mode 100644
index 0000000000..d7416dbc71
--- /dev/null
+++ b/libpurple/plugins/ciphertest.c
@@ -0,0 +1,287 @@
+/*
+ * A plugin to test the ciphers that ship with gaim
+ *
+ * Copyright (C) 2004, Gary Kramlich <amc_grim@users.sf.net>
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifndef GAIM_PLUGINS
+#define GAIM_PLUGINS
+#endif
+
+#include "internal.h"
+
+#include <glib.h>
+#include <string.h>
+
+#include "cipher.h"
+#include "debug.h"
+#include "plugin.h"
+#include "version.h"
+
+struct test {
+ const gchar *question;
+ const gchar *answer;
+};
+
+/**************************************************************************
+ * MD5 Stuff
+ **************************************************************************/
+struct test md5_tests[8] = {
+ { "", "d41d8cd98f00b204e9800998ecf8427e"},
+ { "a", "0cc175b9c0f1b6a831c399e269772661"},
+ { "abc", "900150983cd24fb0d6963f7d28e17f72"},
+ { "message digest", "f96b697d7cb7938d525a2f31aaf161d0"},
+ { "abcdefghijklmnopqrstuvwxyz", "c3fcd3d76192e4007dfb496cca67e13b"},
+ { "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789", "d174ab98d277d9f5a5611c2c9f419d9f"},
+ {"123456789012345678901234567"
+ "890123456789012345678901234"
+ "56789012345678901234567890", "57edf4a22be3c955ac49da2e2107b67a"},
+ { NULL, NULL }
+};
+
+static void
+cipher_test_md5() {
+ GaimCipher *cipher;
+ GaimCipherContext *context;
+ gchar digest[33];
+ gboolean ret;
+ gint i = 0;
+
+ cipher = gaim_ciphers_find_cipher("md5");
+ if(!cipher) {
+ gaim_debug_info("cipher-test",
+ "could not find md5 cipher, not testing\n");
+ return;
+ }
+
+ gaim_debug_info("cipher-test", "Running md5 tests\n");
+
+ context = gaim_cipher_context_new(cipher, NULL);
+
+ while(md5_tests[i].answer) {
+ gaim_debug_info("cipher-test", "Test %02d:\n", i);
+ gaim_debug_info("cipher-test", "Testing '%s'\n", md5_tests[i].question);
+
+ gaim_cipher_context_append(context, (guchar *)md5_tests[i].question,
+ strlen(md5_tests[i].question));
+
+ ret = gaim_cipher_context_digest_to_str(context, sizeof(digest),
+ digest, NULL);
+
+ if(!ret) {
+ gaim_debug_info("cipher-test", "failed\n");
+ } else {
+ gaim_debug_info("cipher-test", "\tGot: %s\n", digest);
+ gaim_debug_info("cipher-test", "\tWanted: %s\n",
+ md5_tests[i].answer);
+ }
+
+ gaim_cipher_context_reset(context, NULL);
+ i++;
+ }
+
+ gaim_cipher_context_destroy(context);
+
+ gaim_debug_info("cipher-test", "md5 tests completed\n\n");
+}
+
+/**************************************************************************
+ * SHA-1 stuff
+ **************************************************************************/
+struct test sha1_tests[5] = {
+ {"a", "86f7e437faa5a7fce15d1ddcb9eaeaea377667b8"},
+ {"abc", "a9993e364706816aba3e25717850c26c9cd0d89d"} ,
+ {"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "84983e441c3bd26ebaae4aa1f95129e5e54670f1"} ,
+ {NULL, "34aa973cd4c4daa4f61eeb2bdbad27316534016f"},
+ {NULL, NULL}
+};
+
+static void
+cipher_test_sha1() {
+ GaimCipher *cipher;
+ GaimCipherContext *context;
+ gchar digest[41];
+ gint i = 0;
+ gboolean ret;
+
+ cipher = gaim_ciphers_find_cipher("sha1");
+ if(!cipher) {
+ gaim_debug_info("cipher-test",
+ "could not find sha1 cipher, not testing\n");
+ return;
+ }
+
+ gaim_debug_info("cipher-test", "Running sha1 tests\n");
+
+ context = gaim_cipher_context_new(cipher, NULL);
+
+ while(sha1_tests[i].answer) {
+ gaim_debug_info("cipher-test", "Test %02d:\n", i);
+ gaim_debug_info("cipher-test", "Testing '%s'\n",
+ (sha1_tests[i].question != NULL) ?
+ sha1_tests[i].question : "'a'x1000, 1000 times");
+
+ if(sha1_tests[i].question) {
+ gaim_cipher_context_append(context, (guchar *)sha1_tests[i].question,
+ strlen(sha1_tests[i].question));
+ } else {
+ gint j;
+ guchar buff[1000];
+
+ memset(buff, 'a', 1000);
+
+ for(j = 0; j < 1000; j++)
+ gaim_cipher_context_append(context, buff, 1000);
+ }
+
+ ret = gaim_cipher_context_digest_to_str(context, sizeof(digest),
+ digest, NULL);
+
+ if(!ret) {
+ gaim_debug_info("cipher-test", "failed\n");
+ } else {
+ gaim_debug_info("cipher-test", "\tGot: %s\n", digest);
+ gaim_debug_info("cipher-test", "\tWanted: %s\n",
+ sha1_tests[i].answer);
+ }
+
+ gaim_cipher_context_reset(context, NULL);
+ i++;
+ }
+
+ gaim_cipher_context_destroy(context);
+
+ gaim_debug_info("cipher-test", "sha1 tests completed\n\n");
+}
+
+static void
+cipher_test_digest()
+{
+ const gchar *nonce = "dcd98b7102dd2f0e8b11d0f600bfb0c093";
+ const gchar *client_nonce = "0a4f113b";
+ const gchar *username = "Mufasa";
+ const gchar *realm = "testrealm@host.com";
+ const gchar *password = "Circle Of Life";
+ const gchar *algorithm = "md5";
+ const gchar *nonce_count = "00000001";
+ const gchar *method = "GET";
+ const gchar *qop = "auth";
+ const gchar *digest_uri = "/dir/index.html";
+ const gchar *entity = NULL;
+
+ gchar *session_key;
+
+ gaim_debug_info("cipher-test", "Running HTTP Digest tests\n");
+
+ session_key = gaim_cipher_http_digest_calculate_session_key(
+ algorithm, username, realm, password,
+ nonce, client_nonce);
+
+ if (session_key == NULL)
+ {
+ gaim_debug_info("cipher-test",
+ "gaim_cipher_http_digest_calculate_session_key failed.\n");
+ }
+ else
+ {
+ gchar *response;
+
+ gaim_debug_info("cipher-test", "\tsession_key: Got: %s\n", session_key);
+ gaim_debug_info("cipher-test", "\tsession_key: Wanted: %s\n", "939e7578ed9e3c518a452acee763bce9");
+
+ response = gaim_cipher_http_digest_calculate_response(
+ algorithm, method, digest_uri, qop, entity,
+ nonce, nonce_count, client_nonce, session_key);
+
+ g_free(session_key);
+
+ if (response == NULL)
+ {
+ gaim_debug_info("cipher-test",
+ "gaim_cipher_http_digest_calculate_session_key failed.\n");
+ }
+ else
+ {
+ gaim_debug_info("cipher-test", "\tresponse: Got: %s\n", response);
+ gaim_debug_info("cipher-test", "\tresponse: Wanted: %s\n", "6629fae49393a05397450978507c4ef1");
+ g_free(response);
+ }
+ }
+
+ gaim_debug_info("cipher-test", "HTTP Digest tests completed\n\n");
+}
+
+/**************************************************************************
+ * Plugin stuff
+ **************************************************************************/
+static gboolean
+plugin_load(GaimPlugin *plugin) {
+ cipher_test_md5();
+ cipher_test_sha1();
+ cipher_test_digest();
+
+ return TRUE;
+}
+
+static gboolean
+plugin_unload(GaimPlugin *plugin) {
+ return TRUE;
+}
+
+static GaimPluginInfo info =
+{
+ GAIM_PLUGIN_MAGIC,
+ GAIM_MAJOR_VERSION,
+ GAIM_MINOR_VERSION,
+ GAIM_PLUGIN_STANDARD, /**< type */
+ NULL, /**< ui_requirement */
+ 0, /**< flags */
+ NULL, /**< dependencies */
+ GAIM_PRIORITY_DEFAULT, /**< priority */
+
+ "core-cipher-test", /**< id */
+ N_("Cipher Test"), /**< name */
+ VERSION, /**< version */
+ /** summary */
+ N_("Tests the ciphers that ship with gaim."),
+ /** description */
+ N_("Tests the ciphers that ship with gaim."),
+ "Gary Kramlich <amc_grim@users.sf.net>", /**< author */
+ GAIM_WEBSITE, /**< homepage */
+
+ plugin_load, /**< load */
+ plugin_unload, /**< unload */
+ NULL, /**< destroy */
+
+ NULL, /**< ui_info */
+ NULL, /**< extra_info */
+ NULL,
+ NULL
+};
+
+static void
+init_plugin(GaimPlugin *plugin) {
+}
+
+GAIM_INIT_PLUGIN(cipher_test, init_plugin, info)
diff --git a/libpurple/plugins/codeinline.c b/libpurple/plugins/codeinline.c
new file mode 100755
index 0000000000..734a918000
--- /dev/null
+++ b/libpurple/plugins/codeinline.c
@@ -0,0 +1,93 @@
+/*
+ * gaim
+ *
+ * Gaim 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 "plugin.h"
+#include "notify.h"
+#include "util.h"
+#include "version.h"
+
+GaimPlugin *plugin_handle = NULL;
+
+static gboolean outgoing_msg_cb(GaimAccount *account, const char *who, char **message,
+ GaimConversation *conv, GaimMessageFlags flags, gpointer null)
+{
+ char *m;
+ char **ms = g_strsplit(*message, "<u>", -1);
+ m = g_strjoinv("<font face=\"monospace\" color=\"#00b025\">", ms);
+ g_strfreev(ms);
+
+ ms = g_strsplit(m, "</u>", -1);
+ g_free(m);
+ m = g_strjoinv("</font>", ms);
+ g_free(*message);
+ *message = m;
+ return FALSE;
+}
+
+static gboolean
+plugin_load(GaimPlugin *plugin)
+{
+ void *handle = gaim_conversations_get_handle();
+ plugin_handle = plugin;
+ gaim_signal_connect(handle, "writing-im-msg", plugin,
+ GAIM_CALLBACK(outgoing_msg_cb), NULL);
+ gaim_signal_connect(handle, "sending-im-msg", plugin,
+ GAIM_CALLBACK(outgoing_msg_cb), NULL);
+
+ return TRUE;
+}
+
+
+static GaimPluginInfo info =
+{
+ GAIM_PLUGIN_MAGIC,
+ GAIM_MAJOR_VERSION,
+ GAIM_MINOR_VERSION,
+ GAIM_PLUGIN_STANDARD,
+ NULL,
+ 0,
+ NULL,
+ GAIM_PRIORITY_DEFAULT,
+ "codeinline",
+ "Code Inline",
+ "1.0",
+ "Formats text as code",
+ "Changes the formatting of any outgoing text such that "
+ "anything underlined will be received green and monospace.",
+ "Sean Egan <seanegan@gmail.com>",
+ "http://gaim.sourceforge.net",
+ plugin_load,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL
+};
+
+ static void
+ init_plugin(GaimPlugin *plugin)
+ {
+ }
+
+GAIM_INIT_PLUGIN(urlcatcher, init_plugin, info)
diff --git a/libpurple/plugins/dbus-buddyicons-example.py b/libpurple/plugins/dbus-buddyicons-example.py
new file mode 100644
index 0000000000..d501370568
--- /dev/null
+++ b/libpurple/plugins/dbus-buddyicons-example.py
@@ -0,0 +1,36 @@
+#!/usr/bin/env python
+#
+# Print the aliases of buddies who have a buddy-icon set.
+#
+# Gaim 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
+#
+
+import dbus
+
+bus = dbus.SessionBus()
+obj = bus.get_object("net.sf.gaim.GaimService", "/net/sf/gaim/GaimObject")
+gaim = dbus.Interface(obj, "net.sf.gaim.GaimInterface")
+
+node = gaim.GaimBlistGetRoot()
+while node != 0:
+ if gaim.GaimBlistNodeIsBuddy(node):
+ icon = gaim.GaimBuddyGetIcon(node)
+ if icon != 0:
+ print gaim.GaimBuddyGetAlias(node)
+ node = gaim.GaimBlistNodeNext(node, 0)
diff --git a/libpurple/plugins/dbus-example.c b/libpurple/plugins/dbus-example.c
new file mode 100644
index 0000000000..ceba0d5e2e
--- /dev/null
+++ b/libpurple/plugins/dbus-example.c
@@ -0,0 +1,178 @@
+/*
+ * This is an example of a gaim dbus plugin. After enabling this
+ * plugin, the following commands should work from the command line:
+ *
+ * prompt$ gaim-send DbusExampleGetHelloObject
+ *
+ * returns, say: int32 74
+ *
+ * prompt$ gaim-send DbusExampleGetText int32:74
+ *
+ * returns: string "Hello."
+ *
+ * prompt$ gaim-send DbusExampleSetText int32:74 string:Bye!
+ *
+ * prompt$ gaim-send DbusExampleGetText int32:74
+ *
+ * returns: string "Bye!"
+ *
+ * Gaim 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 "blist.h"
+#include "notify.h"
+#include "plugin.h"
+#include "version.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define DBUS_API_SUBJECT_TO_CHANGE
+#include "dbus-maybe.h"
+#include "dbus-bindings.h"
+
+typedef struct {
+ char *text;
+} GaimText;
+
+/* This makes the structure GaimText visible to the gaim-dbus type
+ system. It defines GaimText as a type with no parent. From now
+ on, we will be able to register pointers to structures of this
+ type. You to dbus-define types you want to be directly accessible
+ by external applications. */
+GAIM_DBUS_DEFINE_TYPE(GaimText)
+
+/* Here we make four functions accessible to other applications by
+ DBus. These functions can access types defined in gaim proper
+ (GaimBuddy) as well as the types defined in the plugin (GaimText). */
+DBUS_EXPORT GaimText* dbus_example_get_hello_object(void);
+DBUS_EXPORT void dbus_example_set_text(GaimText *obj, const char *text);
+DBUS_EXPORT const char *dbus_example_get_text(GaimText *obj);
+DBUS_EXPORT const char *dbus_example_get_buddy_name(GaimBuddy *buddy);
+
+/* This file has been generated by the #dbus-analize-functions.py
+ script. It contains dbus wrappers for the four functions declared
+ above. */
+#include "dbus-example-bindings.c"
+
+/* This is the GaimText object we want to make publicly visible. */
+static GaimText hello;
+
+/* Here come the definitions of the four exported functions. */
+GaimText* dbus_example_get_hello_object(void)
+{
+ return &hello;
+}
+
+void dbus_example_set_text(GaimText *obj, const char *text)
+{
+ if (obj != NULL) {
+ g_free(obj->text);
+ obj->text = g_strdup(text);
+ }
+}
+
+const char *dbus_example_get_text(GaimText *obj)
+{
+ if (obj != NULL)
+ return obj->text;
+ else
+ return NULL;
+}
+
+const char *dbus_example_get_buddy_name(GaimBuddy *buddy)
+{
+ return gaim_buddy_get_name(buddy);
+}
+
+/* And now standard plugin stuff */
+
+static gboolean
+plugin_load(GaimPlugin *plugin)
+{
+ GAIM_DBUS_RETURN_FALSE_IF_DISABLED(plugin);
+
+ /* First, we have to register our four exported functions with the
+ main gaim dbus loop. Without this statement, the gaim dbus
+ code wouldn't know about our functions. */
+ GAIM_DBUS_REGISTER_BINDINGS(plugin);
+
+ /* Then, we register the hello object of type GaimText. Note that
+ pointer registrations / unregistrations are completely dynamic;
+ they don't have to be made when the plugin is loaded /
+ unloaded. Without this statement the dbus gaim code wouldn't
+ know about the hello object. */
+ GAIM_DBUS_REGISTER_POINTER(&hello, GaimText);
+
+ hello.text = g_strdup("Hello.");
+
+ return TRUE;
+}
+
+
+static gboolean
+plugin_unload(GaimPlugin *plugin)
+{
+ g_free(hello.text);
+
+ /* It is necessary to unregister all pointers registered by the module. */
+ GAIM_DBUS_UNREGISTER_POINTER(&hello);
+
+ return TRUE;
+}
+
+static GaimPluginInfo info =
+{
+ GAIM_PLUGIN_MAGIC,
+ GAIM_MAJOR_VERSION,
+ GAIM_MINOR_VERSION,
+ GAIM_PLUGIN_STANDARD, /**< type */
+ NULL, /**< ui_requirement */
+ 0, /**< flags */
+ NULL, /**< dependencies */
+ GAIM_PRIORITY_DEFAULT, /**< priority */
+
+ "dbus-example", /**< id */
+ N_("DBus Example"), /**< name */
+ VERSION, /**< version */
+ /** summary */
+ N_("DBus Plugin Example"),
+ /** description */
+ N_("DBus Plugin Example"),
+ "Piotr Zielinski (http://cl.cam.ac.uk/~pz215)", /**< author */
+ GAIM_WEBSITE, /**< homepage */
+
+ plugin_load, /**< load */
+ plugin_unload, /**< unload */
+ NULL, /**< destroy */
+
+ NULL, /**< ui_info */
+ NULL, /**< extra_info */
+ NULL, /**< prefs_info */
+ NULL
+};
+
+static void init_plugin(GaimPlugin *plugin)
+{
+}
+
+GAIM_INIT_PLUGIN(dbus_example, init_plugin, info)
diff --git a/libpurple/plugins/filectl.c b/libpurple/plugins/filectl.c
new file mode 100644
index 0000000000..cdcb13adfb
--- /dev/null
+++ b/libpurple/plugins/filectl.c
@@ -0,0 +1,270 @@
+/**
+ * Send commands to Gaim via ~/.gaim/control
+ *
+ * Originally by Eric Warmenhoven <eric@warmenhoven.org>
+ * Compile fixes/mini hacks Alex Bennee <alex@bennee.com>
+ * and Brian Tarricone <bjt23@users.sourceforge.net>
+ */
+
+/* system includes */
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "account.h"
+#include "config.h"
+#include "core.h"
+#include "conversation.h"
+#include "debug.h"
+#include "eventloop.h"
+#include "internal.h"
+#include "util.h"
+#include "version.h"
+
+#define FILECTL_PLUGIN_ID "core-filectl"
+static int check;
+static time_t mtime;
+
+static void init_file(void);
+static gboolean check_file(void);
+
+/* parse char * as if were word array */
+char *getarg(char *, int, int);
+
+/* go through file and run any commands */
+void
+run_commands()
+{
+ struct stat finfo;
+ char filename[256];
+ char buffer[1024];
+ char *command, *arg1, *arg2;
+ FILE *file;
+
+ sprintf(filename, "%s" G_DIR_SEPARATOR_S "control", gaim_user_dir());
+
+ file = g_fopen(filename, "r+");
+ while (fgets(buffer, sizeof(buffer), file)) {
+
+ /* Read the next command */
+ if (buffer[strlen(buffer) - 1] == '\n')
+ buffer[strlen(buffer) - 1] = 0;
+ gaim_debug_misc("filectl", "read: %s\n", buffer);
+ command = getarg(buffer, 0, 0);
+
+ if (!strncasecmp(command, "login", 6)) {
+ GaimAccount *account;
+
+ arg1 = getarg(buffer, 1, 0);
+ arg2 = getarg(buffer, 2, 1);
+
+ account = gaim_accounts_find(arg1, arg2);
+ if (account != NULL) /* username found */
+ gaim_account_connect(account);
+
+ free(arg1);
+ free(arg2);
+
+ } else if (!strncasecmp(command, "logout", 7)) {
+ GaimAccount *account;
+
+ arg1 = getarg(buffer, 1, 1);
+ arg2 = getarg(buffer, 2, 1);
+
+ account = gaim_accounts_find(arg1, arg2);
+ if (account != NULL)
+ {
+ gaim_account_disconnect(account);
+ }
+ else if (arg1 == NULL)
+ gaim_connections_disconnect_all();
+
+ free(arg1);
+ free(arg2);
+
+/* gaim_find_conversation() is gone in 2.0.0. */
+#if 0
+ } else if (!strncasecmp(command, "send", 4)) {
+ GaimConversation *conv;
+
+ arg1 = getarg(buffer, 1, 0);
+ arg2 = getarg(buffer, 2, 1);
+
+ conv = gaim_find_conversation(GAIM_CONV_TYPE_ANY, arg1);
+ if (conv != NULL)
+ {
+ /*
+ gaim_conversation_write(conv, arg2, WFLAG_SEND, NULL, time(NULL), -1);
+ serv_send_im(conv->gc, arg1, arg2, 0);
+ */
+ }
+
+ free(arg1);
+ free(arg2);
+#endif
+
+ } else if (!strncasecmp(command, "away", 4)) {
+ arg1 = getarg(buffer, 1, 1);
+ /* serv_set_away_all(arg1); */
+ free(arg1);
+
+ } else if (!strncasecmp(command, "hide", 4)) {
+ gaim_blist_set_visible(FALSE);
+
+ } else if (!strncasecmp(command, "unhide", 6)) {
+ gaim_blist_set_visible(TRUE);
+
+ } else if (!strncasecmp(command, "back", 4)) {
+ /* do_im_back(); */
+
+ } else if (!strncasecmp(command, "quit", 4)) {
+ gaim_core_quit();
+
+ }
+
+ free(command);
+ }
+
+ fclose(file);
+
+ if (g_stat(filename, &finfo) != 0)
+ return;
+ mtime = finfo.st_mtime;
+}
+
+/**
+ * Check to see if the size of the file is > 0. if so, run commands.
+ */
+void
+init_file()
+{
+ /* most of this was taken from Bash v2.04 by the FSF */
+ struct stat finfo;
+ char filename[256];
+
+ sprintf(filename, "%s" G_DIR_SEPARATOR_S "control", gaim_user_dir());
+
+ if ((g_stat(filename, &finfo) == 0) && (finfo.st_size > 0))
+ run_commands();
+}
+
+/**
+ * Check to see if we need to run commands from the file.
+ */
+gboolean
+check_file()
+{
+ /* most of this was taken from Bash v2.04 by the FSF */
+ struct stat finfo;
+ char filename[256];
+
+ sprintf(filename, "%s" G_DIR_SEPARATOR_S "control", gaim_user_dir());
+
+ if ((g_stat(filename, &finfo) == 0) && (finfo.st_size > 0))
+ {
+ if (mtime != finfo.st_mtime) {
+ gaim_debug_info("filectl", "control changed, checking\n");
+ run_commands();
+ }
+ }
+
+ return TRUE;
+}
+
+char *
+getarg(char *line, int which, int remain)
+{
+ char *arr;
+ char *val;
+ int count = -1;
+ int i;
+ int state = 0;
+
+ for (i = 0; i < strlen(line) && count < which; i++) {
+ switch (state) {
+ case 0: /* in whitespace, expecting word */
+ if (isalnum(line[i])) {
+ count++;
+ state = 1;
+ }
+ break;
+ case 1: /* inside word, waiting for whitespace */
+ if (isspace(line[i])) {
+ state = 0;
+ }
+ break;
+ }
+ }
+
+ arr = strdup(&line[i - 1]);
+ if (remain)
+ return arr;
+
+ for (i = 0; i < strlen(arr) && isalnum(arr[i]); i++);
+ arr[i] = 0;
+ val = strdup(arr);
+ arr[i] = ' ';
+ free(arr);
+ return val;
+}
+
+/*
+ * EXPORTED FUNCTIONS
+ */
+
+static gboolean
+plugin_load(GaimPlugin *plugin)
+{
+ init_file();
+ check = gaim_timeout_add(5000, (GSourceFunc)check_file, NULL);
+
+ return TRUE;
+}
+
+static gboolean
+plugin_unload(GaimPlugin *plugin)
+{
+ gaim_timeout_remove(check);
+
+ return TRUE;
+}
+
+static GaimPluginInfo info =
+{
+ GAIM_PLUGIN_MAGIC,
+ GAIM_MAJOR_VERSION,
+ GAIM_MINOR_VERSION,
+ GAIM_PLUGIN_STANDARD, /**< type */
+ NULL, /**< ui_requirement */
+ 0, /**< flags */
+ NULL, /**< dependencies */
+ GAIM_PRIORITY_DEFAULT, /**< priority */
+
+ FILECTL_PLUGIN_ID, /**< id */
+ N_("Gaim File Control"), /**< name */
+ VERSION, /**< version */
+ /** summary */
+ N_("Allows you to control Gaim by entering commands in a file."),
+ /** description */
+ N_("Allows you to control Gaim by entering commands in a file."),
+ "Eric Warmenhoven <eric@warmenhoven.org>", /**< author */
+ GAIM_WEBSITE, /**< homepage */
+
+ plugin_load, /**< load */
+ plugin_unload, /**< unload */
+ NULL, /**< destroy */
+
+ NULL, /**< ui_info */
+ NULL /**< extra_info */
+};
+
+static void
+init_plugin(GaimPlugin *plugin)
+{
+}
+
+GAIM_INIT_PLUGIN(filectl, init_plugin, info)
diff --git a/libpurple/plugins/fortuneprofile.pl b/libpurple/plugins/fortuneprofile.pl
new file mode 100644
index 0000000000..4a0570efaa
--- /dev/null
+++ b/libpurple/plugins/fortuneprofile.pl
@@ -0,0 +1,125 @@
+# FORTUNE PROFILE
+#
+# Sets your AIM profile to a fortune (with a header and footer of your
+# choice).
+#
+
+# By Sean Egan
+# seanegan@gmail.com
+# AIM: SeanEgn
+#
+# Updated by Nathan Conrad, 31 January 2002
+# Changes:
+# * Fortunes have HTML tabs and newlines
+# AIM: t98502
+# ICQ: 16106363
+#
+# Updated by Mark Doliner, 15 October 2002
+# Changes:
+# * Modified to work with the changed perl interface of gaim 0.60
+# * Fixed a bug where your info would be set to nothing if you had
+# no pre and no post message
+# AIM: lbdash
+#
+# Updated by Christian Hammond, 20 August 2003
+# Changes:
+# * Modified to work with the changed perl interface of gaim 0.68
+# AIM: ChipX86
+
+# Copyright (C) 2001 Sean Egan
+
+# 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
+
+use Gaim;
+
+%PLUGIN_INFO = (
+ perl_api_version => 2,
+ name => "Fortune Profile",
+ version => "3.4",
+ summary => "Sets your AIM profile to a fortune (with a header and footer of your choice).",
+ description => "Sets your AIM profile to a fortune (with a header and footer of your choice).",
+ author => "Sean Egan <seanegan\@gmail.com>",
+ url => "http://gaim.sf.net/",
+
+ load => "plugin_load"
+);
+
+sub plugin_init {
+ return %PLUGIN_INFO;
+}
+
+sub plugin_load {
+ $plugin = shift;
+
+ $tab = "&nbsp;";
+ $tab = $tab . $tab . $tab . $tab;
+ $nl = "<BR>";
+
+ $seconds = 30; # Delay before updating away messages.
+ $max = 1020; # Max length of an profile. It should be
+ # 1024, but I am being safe
+ $pre_message = ""; # This gets added before the fortune
+
+ $post_message ="";
+
+ $len = 0;
+ if ($pre_message ne "") {
+ $len += length( $pre_message . "---$nl" );
+ }
+ if ($post_message ne "") {
+ $len += length("---$nl" . $post_message);
+ }
+
+ # Command to get dynamic message from
+ $command = "fortune -sn " . ($max - $len);
+
+ # output the first message and start the timers...
+ # This is done as a timeout to prevent attempts to set the
+ # profile before logging in.
+ Gaim::timeout_add($plugin, $seconds, \&update_away, 0);
+}
+
+sub update_away {
+ # The fortunes are expanded into HTML (the tabs and newlines) which
+ # causes the -s option of fortune to be a little bit meaningless. This
+ # will loop until it gets a fortune of a good size (after expansion).
+
+ do {
+ do { #It's a while loop because it doesn't always work for some reason
+ $fortune = `$command`;
+ if ($? == -1) {
+ return;
+ }
+ } while ($fortune eq "");
+ $fortune =~ s/\n/$nl/g;
+ $fortune =~ s/\t/$tab/g;
+ } while ((length($fortune) + $len ) > $max);
+
+ $message = $fortune;
+ if ($pre_message ne "") {
+ $message = $pre_message . "---$nl" . $message;
+ }
+ if ($post_message ne "") {
+ $message = $message . "---$nl" . $post_message ;
+ }
+
+ foreach $account (Gaim::accounts()) {
+ if ($account->is_connected()) {
+ $account->set_user_info($message);
+ }
+ }
+
+ Gaim::timeout_add($plugin, $seconds, \&update_away, 0);
+}
diff --git a/libpurple/plugins/gaim.pl b/libpurple/plugins/gaim.pl
new file mode 100644
index 0000000000..c21c6c4f29
--- /dev/null
+++ b/libpurple/plugins/gaim.pl
@@ -0,0 +1,39 @@
+sub description {
+ my($a, $b, $c, $d, $e, $f) = @_;
+ ("Example", "1.0", "An example Gaim perl script that does nothing particularly useful:\n\t-Show a dialog on load\n\t-Set user idle for 6,000 seconds\n\t-Greets people signing on with \"Hello\"\n\t-Informs you when script has been loaded for one minute.", "Eric Warmenhoven &lt;eric\@warmenhoven.org>", "http://gaim.sf.net", "/dev/null");
+}
+
+$handle = GAIM::register("Example", "1.0", "goodbye", "");
+
+GAIM::print("Perl Says", "Handle $handle");
+
+$ver = GAIM::get_info(0);
+@ids = GAIM::get_info(1);
+
+$msg = "Gaim $ver:";
+foreach $id (@ids) {
+ $pro = GAIM::get_info(7, $id);
+ $nam = GAIM::get_info(3, $id);
+ $msg .= "\n$nam using $pro";
+}
+
+
+GAIM::command("idle", 6000);
+
+GAIM::add_event_handler($handle, "event_buddy_signon", "echo_reply");
+GAIM::add_timeout_handler($handle, 60, "notify");
+
+sub echo_reply {
+ $index = $_[0];
+ $who = $_[1];
+ GAIM::print_to_conv($index, $who, "Hello", 0);
+}
+
+sub notify {
+ GAIM::print("1 minute", "gaim test has been loaded for 1 minute");
+}
+
+sub goodbye {
+ GAIM::print("You Bastard!", "You killed Kenny!");
+}
+
diff --git a/libpurple/plugins/idle.c b/libpurple/plugins/idle.c
new file mode 100644
index 0000000000..06fff27458
--- /dev/null
+++ b/libpurple/plugins/idle.c
@@ -0,0 +1,337 @@
+/*
+ * idle.c - I'dle Mak'er plugin for Gaim
+ *
+ * This file is part of Gaim.
+ *
+ * Gaim 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 "connection.h"
+#include "debug.h"
+#include "notify.h"
+#include "plugin.h"
+#include "request.h"
+#include "server.h"
+#include "status.h"
+#include "version.h"
+
+/* This plugin no longer depends on gtk */
+#define IDLE_PLUGIN_ID "core-idle"
+
+static GList *idled_accts = NULL;
+
+static gboolean
+unidle_filter(GaimAccount *acct)
+{
+ if (g_list_find(idled_accts, acct))
+ return TRUE;
+
+ return FALSE;
+}
+
+static gboolean
+idleable_filter(GaimAccount *account)
+{
+ GaimPlugin *prpl;
+
+ prpl = gaim_find_prpl(gaim_account_get_protocol_id(account));
+ g_return_val_if_fail(prpl != NULL, FALSE);
+
+ return (GAIM_PLUGIN_PROTOCOL_INFO(prpl)->set_idle != NULL);
+}
+
+static void
+set_idle_time(GaimAccount *acct, int mins_idle)
+{
+ time_t t;
+ GaimConnection *gc = gaim_account_get_connection(acct);
+ GaimPresence *presence = gaim_account_get_presence(acct);
+
+ if (!gc)
+ return;
+
+ gaim_debug_info("idle",
+ "setting idle time for %s to %d\n",
+ gaim_account_get_username(acct), mins_idle);
+
+ if (mins_idle)
+ t = time(NULL) - (60 * mins_idle); /* subtract seconds idle from current time */
+ else
+ t = 0; /* time idle is irrelevant */
+
+ gaim_presence_set_idle(presence, mins_idle ? TRUE : FALSE, t);
+}
+
+static void
+idle_action_ok(void *ignored, GaimRequestFields *fields)
+{
+ int tm = gaim_request_fields_get_integer(fields, "mins");
+ GaimAccount *acct = gaim_request_fields_get_account(fields, "acct");
+
+ /* only add the account to the GList if it's not already been idled */
+ if (!unidle_filter(acct))
+ {
+ gaim_debug_misc("idle",
+ "%s hasn't been idled yet; adding to list.\n",
+ gaim_account_get_username(acct));
+ idled_accts = g_list_append(idled_accts, acct);
+ }
+
+ set_idle_time(acct, tm);
+}
+
+static void
+idle_all_action_ok(void *ignored, GaimRequestFields *fields)
+{
+ GaimAccount *acct = NULL;
+ GList *list, *iter;
+ int tm = gaim_request_fields_get_integer(fields, "mins");
+ const char *prpl_id = NULL;
+
+ list = gaim_accounts_get_all_active();
+ for(iter = list; iter; iter = iter->next) {
+ acct = (GaimAccount *)(iter->data);
+
+ if(acct)
+ prpl_id = gaim_account_get_protocol_id(acct);
+
+ if(acct && idleable_filter(acct)) {
+ gaim_debug_misc("idle", "Idling %s.\n",
+ gaim_account_get_username(acct));
+
+ set_idle_time(acct, tm);
+
+ if(!g_list_find(idled_accts, acct))
+ idled_accts = g_list_append(idled_accts, acct);
+ }
+ }
+
+ g_list_free(list);
+}
+
+static void
+unidle_action_ok(void *ignored, GaimRequestFields *fields)
+{
+ GaimAccount *acct = gaim_request_fields_get_account(fields, "acct");
+
+ set_idle_time(acct, 0); /* unidle the account */
+
+ /* once the account has been unidled it shouldn't be in the list */
+ idled_accts = g_list_remove(idled_accts, acct);
+}
+
+
+static void
+idle_action(GaimPluginAction *action)
+{
+ /* Use the super fancy request API */
+
+ GaimRequestFields *request;
+ GaimRequestFieldGroup *group;
+ GaimRequestField *field;
+
+ group = gaim_request_field_group_new(NULL);
+
+ field = gaim_request_field_account_new("acct", _("Account"), NULL);
+ gaim_request_field_account_set_filter(field, idleable_filter);
+ gaim_request_field_account_set_show_all(field, FALSE);
+ gaim_request_field_group_add_field(group, field);
+
+ field = gaim_request_field_int_new("mins", _("Minutes"), 10);
+ gaim_request_field_group_add_field(group, field);
+
+ request = gaim_request_fields_new();
+ gaim_request_fields_add_group(request, group);
+
+ gaim_request_fields(action->plugin,
+ N_("I'dle Mak'er"),
+ _("Set Account Idle Time"),
+ NULL,
+ request,
+ _("_Set"), G_CALLBACK(idle_action_ok),
+ _("_Cancel"), NULL,
+ NULL);
+}
+
+static void
+unidle_action(GaimPluginAction *action)
+{
+ GaimRequestFields *request;
+ GaimRequestFieldGroup *group;
+ GaimRequestField *field;
+
+ if (idled_accts == NULL)
+ {
+ gaim_notify_info(NULL, NULL, _("None of your accounts are idle."), NULL);
+ return;
+ }
+
+ group = gaim_request_field_group_new(NULL);
+
+ field = gaim_request_field_account_new("acct", _("Account"), NULL);
+ gaim_request_field_account_set_filter(field, unidle_filter);
+ gaim_request_field_account_set_show_all(field, FALSE);
+ gaim_request_field_group_add_field(group, field);
+
+ request = gaim_request_fields_new();
+ gaim_request_fields_add_group(request, group);
+
+ gaim_request_fields(action->plugin,
+ N_("I'dle Mak'er"),
+ _("Unset Account Idle Time"),
+ NULL,
+ request,
+ _("_Unset"), G_CALLBACK(unidle_action_ok),
+ _("_Cancel"), NULL,
+ NULL);
+}
+
+static void
+idle_all_action(GaimPluginAction *action)
+{
+ GaimRequestFields *request;
+ GaimRequestFieldGroup *group;
+ GaimRequestField *field;
+
+ group = gaim_request_field_group_new(NULL);
+
+ field = gaim_request_field_int_new("mins", _("Minutes"), 10);
+ gaim_request_field_group_add_field(group, field);
+
+ request = gaim_request_fields_new();
+ gaim_request_fields_add_group(request, group);
+
+ gaim_request_fields(action->plugin,
+ N_("I'dle Mak'er"),
+ _("Set Idle Time for All Accounts"),
+ NULL,
+ request,
+ _("_Set"), G_CALLBACK(idle_all_action_ok),
+ _("_Cancel"), NULL,
+ NULL);
+}
+
+static void
+unidle_all_action(GaimPluginAction *action)
+{
+ GList *l;
+
+ /* freeing the list here will cause segfaults if the user idles an account
+ * after the list is freed */
+ for (l = idled_accts; l; l = l->next)
+ {
+ GaimAccount *account = l->data;
+ set_idle_time(account, 0);
+ }
+
+ g_list_free(idled_accts);
+ idled_accts = NULL;
+}
+
+static GList *
+actions(GaimPlugin *plugin, gpointer context)
+{
+ GList *l = NULL;
+ GaimPluginAction *act = NULL;
+
+ act = gaim_plugin_action_new(_("Set Account Idle Time"),
+ idle_action);
+ l = g_list_append(l, act);
+
+ act = gaim_plugin_action_new(_("Unset Account Idle Time"),
+ unidle_action);
+ l = g_list_append(l, act);
+
+ act = gaim_plugin_action_new(_("Set Idle Time for All Accounts"),
+ idle_all_action);
+ l = g_list_append(l, act);
+
+ act = gaim_plugin_action_new(
+ _("Unset Idle Time for All Idled Accounts"), unidle_all_action);
+ l = g_list_append(l, act);
+
+ return l;
+}
+
+static void
+signing_off_cb(GaimConnection *gc, void *data)
+{
+ GaimAccount *account;
+
+ account = gaim_connection_get_account(gc);
+ idled_accts = g_list_remove(idled_accts, account);
+}
+
+static gboolean
+plugin_load(GaimPlugin *plugin)
+{
+ gaim_signal_connect(gaim_connections_get_handle(), "signing-off",
+ plugin,
+ GAIM_CALLBACK(signing_off_cb), NULL);
+
+ return TRUE;
+}
+
+static gboolean
+plugin_unload(GaimPlugin *plugin)
+{
+ unidle_all_action(NULL);
+
+ return TRUE;
+}
+
+static GaimPluginInfo info =
+{
+ GAIM_PLUGIN_MAGIC,
+ GAIM_MAJOR_VERSION,
+ GAIM_MINOR_VERSION,
+ GAIM_PLUGIN_STANDARD,
+ NULL,
+ 0,
+ NULL,
+ GAIM_PRIORITY_DEFAULT,
+ IDLE_PLUGIN_ID,
+
+ /* This is a cultural reference. Dy'er Mak'er is a song by Led Zeppelin.
+ If that doesn't translate well into your language, drop the 's before translating. */
+ N_("I'dle Mak'er"),
+ VERSION,
+ N_("Allows you to hand-configure how long you've been idle"),
+ N_("Allows you to hand-configure how long you've been idle"),
+ "Eric Warmenhoven <eric@warmenhoven.org>",
+ GAIM_WEBSITE,
+ plugin_load,
+ plugin_unload,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ actions
+};
+
+static void
+init_plugin(GaimPlugin *plugin)
+{
+}
+
+
+GAIM_INIT_PLUGIN(idle, init_plugin, info)
+
diff --git a/libpurple/plugins/ipc-test-client.c b/libpurple/plugins/ipc-test-client.c
new file mode 100644
index 0000000000..c31de71f42
--- /dev/null
+++ b/libpurple/plugins/ipc-test-client.c
@@ -0,0 +1,114 @@
+/*
+ * IPC test client plugin.
+ *
+ * Copyright (C) 2003 Christian Hammond.
+ *
+ * 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 "debug.h"
+#include "plugin.h"
+#include "version.h"
+
+#define IPC_TEST_CLIENT_PLUGIN_ID "core-ipc-test-client"
+
+static gboolean
+plugin_load(GaimPlugin *plugin)
+{
+ GaimPlugin *server_plugin;
+ gboolean ok;
+ int result;
+
+ server_plugin = gaim_plugins_find_with_id("core-ipc-test-server");
+
+ if (server_plugin == NULL)
+ {
+ gaim_debug_error("ipc-test-client",
+ "Unable to locate plugin core-ipc-test-server, "
+ "needed for IPC.\n");
+
+ return TRUE;
+ }
+
+ result = (int)gaim_plugin_ipc_call(server_plugin, "add", &ok, 36, 6);
+
+ if (!ok)
+ {
+ gaim_debug_error("ipc-test-client",
+ "Unable to call IPC function 'add' in "
+ "core-ipc-test-server plugin.");
+
+ return TRUE;
+ }
+
+ gaim_debug_info("ipc-test-client", "36 + 6 = %d\n", result);
+
+ result = (int)gaim_plugin_ipc_call(server_plugin, "sub", &ok, 50, 8);
+
+ if (!ok)
+ {
+ gaim_debug_error("ipc-test-client",
+ "Unable to call IPC function 'sub' in "
+ "core-ipc-test-server plugin.");
+
+ return TRUE;
+ }
+
+ gaim_debug_info("ipc-test-client", "50 - 8 = %d\n", result);
+
+ return TRUE;
+}
+
+static GaimPluginInfo info =
+{
+ GAIM_PLUGIN_MAGIC,
+ GAIM_MAJOR_VERSION,
+ GAIM_MINOR_VERSION,
+ GAIM_PLUGIN_STANDARD, /**< type */
+ NULL, /**< ui_requirement */
+ 0, /**< flags */
+ NULL, /**< dependencies */
+ GAIM_PRIORITY_DEFAULT, /**< priority */
+
+ IPC_TEST_CLIENT_PLUGIN_ID, /**< id */
+ N_("IPC Test Client"), /**< name */
+ VERSION, /**< version */
+ /** summary */
+ N_("Test plugin IPC support, as a client."),
+ /** description */
+ N_("Test plugin IPC support, as a client. This locates the server "
+ "plugin and calls the commands registered."),
+ "Christian Hammond <chipx86@gnupdate.org>", /**< author */
+ GAIM_WEBSITE, /**< homepage */
+
+ plugin_load, /**< load */
+ NULL, /**< unload */
+ NULL, /**< destroy */
+
+ NULL, /**< ui_info */
+ NULL, /**< extra_info */
+ NULL,
+ NULL
+};
+
+static void
+init_plugin(GaimPlugin *plugin)
+{
+ info.dependencies = g_list_append(info.dependencies,
+ "core-ipc-test-server");
+}
+
+GAIM_INIT_PLUGIN(ipctestclient, init_plugin, info)
diff --git a/libpurple/plugins/ipc-test-server.c b/libpurple/plugins/ipc-test-server.c
new file mode 100644
index 0000000000..f082661e9c
--- /dev/null
+++ b/libpurple/plugins/ipc-test-server.c
@@ -0,0 +1,99 @@
+/*
+ * IPC test server plugin.
+ *
+ * Copyright (C) 2003 Christian Hammond.
+ *
+ * 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.
+ */
+#define IPC_TEST_SERVER_PLUGIN_ID "core-ipc-test-server"
+
+#include "internal.h"
+#include "debug.h"
+#include "plugin.h"
+#include "version.h"
+
+static int
+add_func(int i1, int i2)
+{
+ gaim_debug_misc("ipc-test-server", "Got %d, %d, returning %d\n",
+ i1, i2, i1 + i2);
+ return i1 + i2;
+}
+
+static int
+sub_func(int i1, int i2)
+{
+ gaim_debug_misc("ipc-test-server", "Got %d, %d, returning %d\n",
+ i1, i2, i1 - i2);
+ return i1 - i2;
+}
+
+static gboolean
+plugin_load(GaimPlugin *plugin)
+{
+ gaim_plugin_ipc_register(plugin, "add", GAIM_CALLBACK(add_func),
+ gaim_marshal_INT__INT_INT,
+ gaim_value_new(GAIM_TYPE_INT), 2,
+ gaim_value_new(GAIM_TYPE_INT),
+ gaim_value_new(GAIM_TYPE_INT));
+
+ gaim_plugin_ipc_register(plugin, "sub", GAIM_CALLBACK(sub_func),
+ gaim_marshal_INT__INT_INT,
+ gaim_value_new(GAIM_TYPE_INT), 2,
+ gaim_value_new(GAIM_TYPE_INT),
+ gaim_value_new(GAIM_TYPE_INT));
+
+ return TRUE;
+}
+
+static GaimPluginInfo info =
+{
+ GAIM_PLUGIN_MAGIC,
+ GAIM_MAJOR_VERSION,
+ GAIM_MINOR_VERSION,
+ GAIM_PLUGIN_STANDARD, /**< type */
+ NULL, /**< ui_requirement */
+ 0, /**< flags */
+ NULL, /**< dependencies */
+ GAIM_PRIORITY_DEFAULT, /**< priority */
+
+ IPC_TEST_SERVER_PLUGIN_ID, /**< id */
+ N_("IPC Test Server"), /**< name */
+ VERSION, /**< version */
+ /** summary */
+ N_("Test plugin IPC support, as a server."),
+ /** description */
+ N_("Test plugin IPC support, as a server. This registers the IPC "
+ "commands."),
+ "Christian Hammond <chipx86@gnupdate.org>", /**< author */
+ GAIM_WEBSITE, /**< homepage */
+
+ plugin_load, /**< load */
+ NULL, /**< unload */
+ NULL, /**< destroy */
+
+ NULL, /**< ui_info */
+ NULL, /**< extra_info */
+ NULL,
+ NULL
+};
+
+static void
+init_plugin(GaimPlugin *plugin)
+{
+}
+
+GAIM_INIT_PLUGIN(ipctestserver, init_plugin, info)
diff --git a/libpurple/plugins/log_reader.c b/libpurple/plugins/log_reader.c
new file mode 100644
index 0000000000..826aeed664
--- /dev/null
+++ b/libpurple/plugins/log_reader.c
@@ -0,0 +1,2151 @@
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+
+#ifndef GAIM_PLUGINS
+# define GAIM_PLUGINS
+#endif
+
+#include "internal.h"
+
+#include "debug.h"
+#include "log.h"
+#include "plugin.h"
+#include "pluginpref.h"
+#include "prefs.h"
+#include "stringref.h"
+#include "util.h"
+#include "version.h"
+#include "xmlnode.h"
+
+/* This must be the last Gaim header included. */
+#ifdef _WIN32
+#include "win32dep.h"
+#endif
+
+/* Where is the Windows partition mounted? */
+#ifndef GAIM_LOG_READER_WINDOWS_MOUNT_POINT
+#define GAIM_LOG_READER_WINDOWS_MOUNT_POINT "/mnt/windows"
+#endif
+
+enum name_guesses {
+ NAME_GUESS_UNKNOWN,
+ NAME_GUESS_ME,
+ NAME_GUESS_THEM
+};
+
+
+/*****************************************************************************
+ * Adium Logger *
+ *****************************************************************************/
+
+/* The adium logger doesn't write logs, only reads them. This is to include
+ * Adium logs in the log viewer transparently.
+ */
+
+static GaimLogLogger *adium_logger;
+
+enum adium_log_type {
+ ADIUM_HTML,
+ ADIUM_TEXT,
+};
+
+struct adium_logger_data {
+ char *path;
+ enum adium_log_type type;
+};
+
+static GList *adium_logger_list(GaimLogType type, const char *sn, GaimAccount *account)
+{
+ GList *list = NULL;
+ const char *logdir;
+ GaimPlugin *plugin;
+ GaimPluginProtocolInfo *prpl_info;
+ char *prpl_name;
+ char *temp;
+ char *path;
+ GDir *dir;
+
+ g_return_val_if_fail(sn != NULL, list);
+ g_return_val_if_fail(account != NULL, list);
+
+ logdir = gaim_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;
+
+ plugin = gaim_find_prpl(gaim_account_get_protocol_id(account));
+ if (!plugin)
+ return NULL;
+
+ prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(plugin);
+ if (!prpl_info->list_icon)
+ return NULL;
+
+ prpl_name = g_ascii_strup(prpl_info->list_icon(account, NULL), -1);
+
+ temp = g_strdup_printf("%s.%s", prpl_name, account->username);
+ path = g_build_filename(logdir, temp, sn, NULL);
+ g_free(temp);
+
+ dir = g_dir_open(path, 0, NULL);
+ if (dir) {
+ const gchar *file;
+
+ while ((file = g_dir_read_name(dir))) {
+ if (!gaim_str_has_prefix(file, sn))
+ continue;
+ if (gaim_str_has_suffix(file, ".html") || gaim_str_has_suffix(file, ".AdiumHTMLLog")) {
+ struct tm tm;
+ const char *date = file;
+
+ date += strlen(sn) + 2;
+ if (sscanf(date, "%u|%u|%u",
+ &tm.tm_year, &tm.tm_mon, &tm.tm_mday) != 3) {
+
+ gaim_debug(GAIM_DEBUG_ERROR, "Adium log parse",
+ "Filename timestamp parsing error\n");
+ } else {
+ char *filename = g_build_filename(path, file, NULL);
+ FILE *handle = g_fopen(filename, "rb");
+ char *contents;
+ char *contents2;
+ struct adium_logger_data *data;
+ GaimLog *log;
+
+ if (!handle) {
+ g_free(filename);
+ continue;
+ }
+
+ /* XXX: This is really inflexible. */
+ contents = g_malloc(57);
+ fread(contents, 56, 1, handle);
+ fclose(handle);
+ contents[56] = '\0';
+
+ /* XXX: This is fairly inflexible. */
+ contents2 = contents;
+ while (*contents2 && *contents2 != '>')
+ contents2++;
+ if (*contents2)
+ contents2++;
+ while (*contents2 && *contents2 != '>')
+ contents2++;
+ if (*contents2)
+ contents2++;
+
+ if (sscanf(contents2, "%u.%u.%u",
+ &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 3) {
+
+ gaim_debug(GAIM_DEBUG_ERROR, "Adium log parse",
+ "Contents timestamp parsing error\n");
+ g_free(contents);
+ g_free(filename);
+ continue;
+ }
+ g_free(contents);
+
+ data = g_new0(struct adium_logger_data, 1);
+ data->path = filename;
+ data->type = ADIUM_HTML;
+
+ tm.tm_year -= 1900;
+ tm.tm_mon -= 1;
+
+ /* XXX: Look into this later... Should we pass in a struct tm? */
+ log = gaim_log_new(GAIM_LOG_IM, sn, account, NULL, mktime(&tm), NULL);
+ log->logger = adium_logger;
+ log->logger_data = data;
+
+ list = g_list_append(list, log);
+ }
+ } else if (gaim_str_has_suffix(file, ".adiumLog")) {
+ struct tm tm;
+ const char *date = file;
+
+ date += strlen(sn) + 2;
+ if (sscanf(date, "%u|%u|%u",
+ &tm.tm_year, &tm.tm_mon, &tm.tm_mday) != 3) {
+
+ gaim_debug(GAIM_DEBUG_ERROR, "Adium log parse",
+ "Filename timestamp parsing error\n");
+ } else {
+ char *filename = g_build_filename(path, file, NULL);
+ FILE *handle = g_fopen(filename, "rb");
+ char *contents;
+ char *contents2;
+ struct adium_logger_data *data;
+ GaimLog *log;
+
+ if (!handle) {
+ g_free(filename);
+ continue;
+ }
+
+ /* XXX: This is really inflexible. */
+ contents = g_malloc(14);
+ fread(contents, 13, 1, handle);
+ fclose(handle);
+ contents[13] = '\0';
+
+ contents2 = contents;
+ while (*contents2 && *contents2 != '(')
+ contents2++;
+ if (*contents2)
+ contents2++;
+
+ if (sscanf(contents2, "%u.%u.%u",
+ &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 3) {
+
+ gaim_debug(GAIM_DEBUG_ERROR, "Adium log parse",
+ "Contents timestamp parsing error\n");
+ g_free(contents);
+ g_free(filename);
+ continue;
+ }
+
+ g_free(contents);
+
+ tm.tm_year -= 1900;
+ tm.tm_mon -= 1;
+
+ data = g_new0(struct adium_logger_data, 1);
+ data->path = filename;
+ data->type = ADIUM_TEXT;
+
+ /* XXX: Look into this later... Should we pass in a struct tm? */
+ log = gaim_log_new(GAIM_LOG_IM, sn, account, NULL, mktime(&tm), NULL);
+ log->logger = adium_logger;
+ log->logger_data = data;
+
+ list = g_list_append(list, log);
+ }
+ }
+ }
+ g_dir_close(dir);
+ }
+
+ g_free(prpl_name);
+ g_free(path);
+
+ return list;
+}
+
+static char *adium_logger_read (GaimLog *log, GaimLogReadFlags *flags)
+{
+ struct adium_logger_data *data;
+ GError *error = NULL;
+ gchar *read = NULL;
+ gsize length;
+
+ g_return_val_if_fail(log != NULL, g_strdup(""));
+
+ data = log->logger_data;
+
+ g_return_val_if_fail(data->path != NULL, g_strdup(""));
+
+ gaim_debug(GAIM_DEBUG_INFO, "Adium log read",
+ "Reading %s\n", data->path);
+ if (!g_file_get_contents(data->path, &read, &length, &error)) {
+ gaim_debug(GAIM_DEBUG_ERROR, "Adium log read",
+ "Error reading log\n");
+ if (error)
+ g_error_free(error);
+ return g_strdup("");
+ }
+
+ if (data->type != ADIUM_HTML) {
+ char *escaped = g_markup_escape_text(read, -1);
+ g_free(read);
+ read = escaped;
+ }
+
+#ifdef WIN32
+ /* This problem only seems to show up on Windows.
+ * The BOM is displaying as a space at the beginning of the log.
+ */
+ if (gaim_str_has_prefix(read, "\xef\xbb\xbf"))
+ {
+ /* FIXME: This feels so wrong... */
+ char *temp = g_strdup(&(read[3]));
+ g_free(read);
+ read = temp;
+ }
+#endif
+
+ /* TODO: Apply formatting.
+ * Replace the above hack with something better, since we'll
+ * be looping over the entire log file contents anyway.
+ */
+
+ return read;
+}
+
+static int adium_logger_size (GaimLog *log)
+{
+ struct adium_logger_data *data;
+ char *text;
+ size_t size;
+
+ g_return_val_if_fail(log != NULL, 0);
+
+ data = log->logger_data;
+
+ if (gaim_prefs_get_bool("/plugins/core/log_reader/fast_sizes")) {
+ struct stat st;
+
+ if (!data->path || stat(data->path, &st))
+ st.st_size = 0;
+
+ return st.st_size;
+ }
+
+ text = adium_logger_read(log, NULL);
+ size = strlen(text);
+ g_free(text);
+
+ return size;
+}
+
+static void adium_logger_finalize(GaimLog *log)
+{
+ struct adium_logger_data *data;
+
+ g_return_if_fail(log != NULL);
+
+ data = log->logger_data;
+
+ g_free(data->path);
+}
+
+
+/*****************************************************************************
+ * Fire Logger *
+ *****************************************************************************/
+
+#if 0
+/* The fire logger doesn't write logs, only reads them. This is to include
+ * Fire logs in the log viewer transparently.
+ */
+
+static GaimLogLogger *fire_logger;
+
+struct fire_logger_data {
+};
+
+static GList *fire_logger_list(GaimLogType type, const char *sn, GaimAccount *account)
+{
+ /* TODO: Do something here. */
+ return NULL;
+}
+
+static char * fire_logger_read (GaimLog *log, GaimLogReadFlags *flags)
+{
+ struct fire_logger_data *data;
+
+ g_return_val_if_fail(log != NULL, g_strdup(""));
+
+ data = log->logger_data;
+
+ /* TODO: Do something here. */
+ return g_strdup("");
+}
+
+static int fire_logger_size (GaimLog *log)
+{
+ g_return_val_if_fail(log != NULL, 0);
+
+ if (gaim_prefs_get_bool("/plugins/core/log_reader/fast_sizes"))
+ return 0;
+
+ /* TODO: Do something here. */
+ return 0;
+}
+
+static void fire_logger_finalize(GaimLog *log)
+{
+ g_return_if_fail(log != NULL);
+
+ /* TODO: Do something here. */
+}
+#endif
+
+
+/*****************************************************************************
+ * Messenger Plus! Logger *
+ *****************************************************************************/
+
+#if 0
+/* The messenger_plus logger doesn't write logs, only reads them. This is to include
+ * Messenger Plus! logs in the log viewer transparently.
+ */
+
+static GaimLogLogger *messenger_plus_logger;
+
+struct messenger_plus_logger_data {
+};
+
+static GList *messenger_plus_logger_list(GaimLogType type, const char *sn, GaimAccount *account)
+{
+ /* TODO: Do something here. */
+ return NULL;
+}
+
+static char * messenger_plus_logger_read (GaimLog *log, GaimLogReadFlags *flags)
+{
+ struct messenger_plus_logger_data *data = log->logger_data;
+
+ g_return_val_if_fail(log != NULL, g_strdup(""));
+
+ data = log->logger_data;
+
+ /* TODO: Do something here. */
+ return g_strdup("");
+}
+
+static int messenger_plus_logger_size (GaimLog *log)
+{
+ g_return_val_if_fail(log != NULL, 0);
+
+ if (gaim_prefs_get_bool("/plugins/core/log_reader/fast_sizes"))
+ return 0;
+
+ /* TODO: Do something here. */
+ return 0;
+}
+
+static void messenger_plus_logger_finalize(GaimLog *log)
+{
+ g_return_if_fail(log != NULL);
+
+ /* TODO: Do something here. */
+}
+#endif
+
+
+/*****************************************************************************
+ * MSN Messenger Logger *
+ *****************************************************************************/
+
+/* The msn logger doesn't write logs, only reads them. This is to include
+ * MSN Messenger message histories in the log viewer transparently.
+ */
+
+static GaimLogLogger *msn_logger;
+
+struct msn_logger_data {
+ xmlnode *root;
+ xmlnode *message;
+ const char *session_id;
+ int last_log;
+ GString *text;
+};
+
+/* This function is really confusing. It makes baby rlaager cry...
+ In other news: "You lost a lot of blood but we found most of it."
+ */
+static time_t msn_logger_parse_timestamp(xmlnode *message, struct tm **tm_out)
+{
+ const char *datetime;
+ static struct tm tm2;
+ time_t stamp;
+ const char *date;
+ const char *time;
+ int month;
+ int day;
+ int year;
+ int hour;
+ int min;
+ int sec;
+ char am_pm;
+ char *str;
+ static struct tm tm;
+ time_t t;
+ time_t diff;
+
+#ifndef G_DISABLE_CHECKS
+ if (message != NULL)
+ {
+ *tm_out = NULL;
+
+ /* Trigger the usual warning. */
+ g_return_val_if_fail(message != NULL, (time_t)0);
+ }
+#endif
+
+ datetime = xmlnode_get_attrib(message, "DateTime");
+ if (!(datetime && *datetime))
+ {
+ gaim_debug_error("MSN log timestamp parse",
+ "Attribute missing: %s\n", "DateTime");
+ return (time_t)0;
+ }
+
+ stamp = gaim_str_to_time(datetime, TRUE, &tm2, NULL, NULL);
+#ifdef HAVE_TM_GMTOFF
+ tm2.tm_gmtoff = 0;
+#endif
+#ifdef HAVE_STRUCT_TM_TM_ZONE
+ /* This is used in the place of a timezone abbreviation if the
+ * offset is way off. The user should never really see it, but
+ * it's here just in case. The parens are to make it clear it's
+ * not a real timezone. */
+ tm2.tm_zone = _("(UTC)");
+#endif
+
+
+ date = xmlnode_get_attrib(message, "Date");
+ if (!(date && *date))
+ {
+ gaim_debug_error("MSN log timestamp parse",
+ "Attribute missing: %s\n", "Date");
+ *tm_out = &tm2;
+ return stamp;
+ }
+
+ time = xmlnode_get_attrib(message, "Time");
+ if (!(time && *time))
+ {
+ gaim_debug_error("MSN log timestamp parse",
+ "Attribute missing: %s\n", "Time");
+ *tm_out = &tm2;
+ return stamp;
+ }
+
+ if (sscanf(date, "%u/%u/%u", &month, &day, &year) != 3)
+ {
+ gaim_debug_error("MSN log timestamp parse",
+ "%s parsing error\n", "Date");
+ *tm_out = &tm2;
+ return stamp;
+ }
+ else
+ {
+ if (month > 12)
+ {
+ int tmp = day;
+ day = month;
+ month = tmp;
+ }
+ }
+
+ if (sscanf(time, "%u:%u:%u %c", &hour, &min, &sec, &am_pm) != 4)
+ {
+ gaim_debug_error("MSN log timestamp parse",
+ "%s parsing error\n", "Time");
+ *tm_out = &tm2;
+ return stamp;
+ }
+
+ if (am_pm == 'P') {
+ hour += 12;
+ } else if (hour == 12) {
+ /* 12 AM = 00 hr */
+ hour = 0;
+ }
+
+ str = g_strdup_printf("%04i-%02i-%02iT%02i:%02i:%02i", year, month, day, hour, min, sec);
+ t = gaim_str_to_time(str, TRUE, &tm, NULL, NULL);
+
+
+ if (stamp > t)
+ diff = stamp - t;
+ else
+ diff = t - stamp;
+
+ if (diff > (14 * 60 * 60))
+ {
+ if (day <= 12)
+ {
+ /* Swap day & month variables, to see if it's a non-US date. */
+ g_free(str);
+ str = g_strdup_printf("%04i-%02i-%02iT%02i:%02i:%02i", year, month, day, hour, min, sec);
+ t = gaim_str_to_time(str, TRUE, &tm, NULL, NULL);
+
+ if (stamp > t)
+ diff = stamp - t;
+ else
+ diff = t - stamp;
+
+ if (diff > (14 * 60 * 60))
+ {
+ /* We got a time, it's not impossible, but
+ * the diff is too large. Display the UTC time. */
+ g_free(str);
+ *tm_out = &tm2;
+ return stamp;
+ }
+ else
+ {
+ /* Legal time */
+ /* Fall out */
+ }
+ }
+ else
+ {
+ /* We got a time, it's not impossible, but
+ * the diff is too large. Display the UTC time. */
+ g_free(str);
+ *tm_out = &tm2;
+ return stamp;
+ }
+ }
+
+ /* If we got here, the time is legal with a reasonable offset.
+ * Let's find out if it's in our TZ. */
+ if (gaim_str_to_time(str, FALSE, &tm, NULL, NULL) == stamp)
+ {
+ g_free(str);
+ *tm_out = &tm;
+ return stamp;
+ }
+ g_free(str);
+
+ /* The time isn't in our TZ, but it's reasonable. */
+#ifdef HAVE_STRUCT_TM_TM_ZONE
+ tm.tm_zone = " ";
+#endif
+ *tm_out = &tm;
+ return stamp;
+}
+
+static GList *msn_logger_list(GaimLogType type, const char *sn, GaimAccount *account)
+{
+ GList *list = NULL;
+ char *username;
+ GaimBuddy *buddy;
+ const char *logdir;
+ const char *savedfilename = NULL;
+ char *logfile;
+ char *path;
+ GError *error = NULL;
+ gchar *contents = NULL;
+ gsize length;
+ xmlnode *root;
+ xmlnode *message;
+ 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);
+
+ if (strcmp(account->protocol_id, "prpl-msn"))
+ return list;
+
+ logdir = gaim_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;
+
+ buddy = gaim_find_buddy(account, sn);
+
+ if ((username = g_strdup(gaim_account_get_string(
+ account, "log_reader_msn_log_folder", NULL)))) {
+ /* As a special case, we allow the null string to kill the parsing
+ * straight away. This would allow the user to deal with the case
+ * when two account have the same username at different domains and
+ * only one has logs stored.
+ */
+ if (!*username) {
+ g_free(username);
+ return list;
+ }
+ } else {
+ username = g_strdup(gaim_normalize(account, account->username));
+ }
+
+ if (buddy)
+ savedfilename = gaim_blist_node_get_string(&buddy->node, "log_reader_msn_log_filename");
+
+ if (savedfilename) {
+ /* As a special case, we allow the null string to kill the parsing
+ * straight away. This would allow the user to deal with the case
+ * when two buddies have the same username at different domains and
+ * only one has logs stored.
+ */
+ if (!*savedfilename) {
+ g_free(username);
+ return list;
+ }
+
+ logfile = g_strdup(savedfilename);
+ } else {
+ logfile = g_strdup_printf("%s.xml", gaim_normalize(account, sn));
+ }
+
+ path = g_build_filename(logdir, username, "History", logfile, NULL);
+
+ if (!g_file_test(path, G_FILE_TEST_EXISTS)) {
+ gboolean found = FALSE;
+ char *at_sign;
+ GDir *dir;
+
+ g_free(path);
+
+ if (savedfilename) {
+ /* We had a saved filename, but it doesn't exist.
+ * Returning now is the right course of action because we don't
+ * want to detect another file incorrectly.
+ */
+ g_free(username);
+ g_free(logfile);
+ return list;
+ }
+
+ /* Perhaps we're using a new version of MSN with the weird numbered folders.
+ * I don't know how the numbers are calculated, so I'm going to attempt to
+ * find logs by pattern matching...
+ */
+
+ at_sign = g_strrstr(username, "@");
+ if (at_sign)
+ *at_sign = '\0';
+
+ dir = g_dir_open(logdir, 0, NULL);
+ if (dir) {
+ const gchar *name;
+
+ while ((name = g_dir_read_name(dir))) {
+ const char *c = name;
+
+ if (!gaim_str_has_prefix(c, username))
+ continue;
+
+ c += strlen(username);
+ while (*c) {
+ if (!g_ascii_isdigit(*c))
+ break;
+
+ c++;
+ }
+
+ path = g_build_filename(logdir, name, NULL);
+ /* The !c makes sure we got to the end of the while loop above. */
+ if (!*c && g_file_test(path, G_FILE_TEST_IS_DIR)) {
+ char *history_path = g_build_filename(
+ path, "History", NULL);
+ if (g_file_test(history_path, G_FILE_TEST_IS_DIR)) {
+ gaim_account_set_string(account,
+ "log_reader_msn_log_folder", name);
+ g_free(path);
+ path = history_path;
+ found = TRUE;
+ break;
+ }
+ g_free(path);
+ g_free(history_path);
+ }
+ else
+ g_free(path);
+ }
+ g_dir_close(dir);
+ }
+ g_free(username);
+
+ if (!found) {
+ g_free(logfile);
+ return list;
+ }
+
+ /* If we've reached this point, we've found a History folder. */
+
+ username = g_strdup(gaim_normalize(account, sn));
+ at_sign = g_strrstr(username, "@");
+ if (at_sign)
+ *at_sign = '\0';
+
+ found = FALSE;
+ dir = g_dir_open(path, 0, NULL);
+ if (dir) {
+ const gchar *name;
+
+ while ((name = g_dir_read_name(dir))) {
+ const char *c = name;
+
+ if (!gaim_str_has_prefix(c, username))
+ continue;
+
+ c += strlen(username);
+ while (*c) {
+ if (!g_ascii_isdigit(*c))
+ break;
+
+ c++;
+ }
+
+ path = g_build_filename(path, name, NULL);
+ if (!strcmp(c, ".xml") &&
+ g_file_test(path, G_FILE_TEST_EXISTS)) {
+ found = TRUE;
+ g_free(logfile);
+ logfile = g_strdup(name);
+ break;
+ }
+ else
+ g_free(path);
+ }
+ g_dir_close(dir);
+ }
+ g_free(username);
+
+ if (!found) {
+ g_free(logfile);
+ return list;
+ }
+ } else {
+ g_free(username);
+ g_free(logfile);
+ logfile = NULL; /* No sense saving the obvious buddy@domain.com. */
+ }
+
+ gaim_debug(GAIM_DEBUG_INFO, "MSN log read",
+ "Reading %s\n", path);
+ if (!g_file_get_contents(path, &contents, &length, &error)) {
+ g_free(path);
+ gaim_debug(GAIM_DEBUG_ERROR, "MSN log read",
+ "Error reading log\n");
+ if (error)
+ g_error_free(error);
+ return list;
+ }
+ g_free(path);
+
+ /* Reading the file was successful...
+ * Save its name if it involves the crazy numbers. The idea here is that you could
+ * then tweak the blist.xml file by hand if need be. This would be the case if two
+ * buddies have the same username at different domains. One set of logs would get
+ * detected for both buddies.
+ */
+ if (buddy && logfile) {
+ gaim_blist_node_set_string(&buddy->node, "log_reader_msn_log_filename", logfile);
+ g_free(logfile);
+ }
+
+ root = xmlnode_from_str(contents, length);
+ g_free(contents);
+ if (!root)
+ return list;
+
+ for (message = xmlnode_get_child(root, "Message"); message;
+ message = xmlnode_get_next_twin(message)) {
+ const char *session_id;
+
+ session_id = xmlnode_get_attrib(message, "SessionID");
+ if (!session_id) {
+ gaim_debug(GAIM_DEBUG_ERROR, "MSN log parse",
+ "Error parsing message: %s\n", "SessionID missing");
+ continue;
+ }
+
+ if (strcmp(session_id, old_session_id)) {
+ /*
+ * The session ID differs from the last message.
+ * Thus, this is the start of a new conversation.
+ */
+ struct tm *tm;
+ time_t stamp;
+ GaimLog *log;
+
+ data = g_new0(struct msn_logger_data, 1);
+ data->root = root;
+ data->message = message;
+ data->session_id = session_id;
+ data->text = NULL;
+ data->last_log = FALSE;
+
+ stamp = msn_logger_parse_timestamp(message, &tm);
+
+ log = gaim_log_new(GAIM_LOG_IM, sn, account, NULL, stamp, tm);
+ log->logger = msn_logger;
+ log->logger_data = data;
+
+ list = g_list_append(list, log);
+ }
+ old_session_id = session_id;
+ }
+
+ if (data)
+ data->last_log = TRUE;
+
+ return list;
+}
+
+static char * msn_logger_read (GaimLog *log, GaimLogReadFlags *flags)
+{
+ struct msn_logger_data *data;
+ GString *text = NULL;
+ xmlnode *message;
+
+ g_return_val_if_fail(log != NULL, g_strdup(""));
+
+ data = log->logger_data;
+
+ if (data->text) {
+ /* The GTK code which displays the logs g_free()s whatever is
+ * returned from this function. Thus, we can't reuse the str
+ * part of the GString. The only solution is to free it and
+ * start over.
+ */
+ g_string_free(data->text, FALSE);
+ }
+
+ text = g_string_new("");
+
+ if (!data->root || !data->message || !data->session_id) {
+ /* Something isn't allocated correctly. */
+ gaim_debug(GAIM_DEBUG_ERROR, "MSN log parse",
+ "Error parsing message: %s\n", "Internal variables inconsistent");
+ data->text = text;
+
+ return text->str;
+ }
+
+ for (message = data->message; message;
+ message = xmlnode_get_next_twin(message)) {
+
+ const char *new_session_id;
+ xmlnode *text_node;
+ const char *from_name = NULL;
+ const char *to_name = NULL;
+ xmlnode *from;
+ xmlnode *to;
+ enum name_guesses name_guessed = NAME_GUESS_UNKNOWN;
+ const char *their_name;
+ time_t time_unix;
+ struct tm *tm;
+ char *timestamp;
+ char *tmp;
+ const char *style;
+
+ new_session_id = xmlnode_get_attrib(message, "SessionID");
+
+ /* If this triggers, something is wrong with the XML. */
+ if (!new_session_id) {
+ gaim_debug(GAIM_DEBUG_ERROR, "MSN log parse",
+ "Error parsing message: %s\n", "New SessionID missing");
+ break;
+ }
+
+ if (strcmp(new_session_id, data->session_id)) {
+ /* The session ID differs from the first message.
+ * Thus, this is the start of a new conversation.
+ */
+ break;
+ }
+
+ text_node = xmlnode_get_child(message, "Text");
+ if (!text_node)
+ continue;
+
+ from = xmlnode_get_child(message, "From");
+ if (from) {
+ xmlnode *user = xmlnode_get_child(from, "User");
+
+ if (user) {
+ from_name = xmlnode_get_attrib(user, "FriendlyName");
+
+ /* This saves a check later. */
+ if (!*from_name)
+ from_name = NULL;
+ }
+ }
+
+ to = xmlnode_get_child(message, "To");
+ if (to) {
+ xmlnode *user = xmlnode_get_child(to, "User");
+ if (user) {
+ to_name = xmlnode_get_attrib(user, "FriendlyName");
+
+ /* This saves a check later. */
+ if (!*to_name)
+ to_name = NULL;
+ }
+ }
+
+ their_name = from_name;
+ if (from_name && gaim_prefs_get_bool("/plugins/core/log_reader/use_name_heuristics")) {
+ const char *friendly_name = gaim_connection_get_display_name(log->account->gc);
+
+ if (friendly_name != NULL) {
+ int friendly_name_length = strlen(friendly_name);
+ const char *alias;
+ int alias_length;
+ GaimBuddy *buddy = gaim_find_buddy(log->account, log->name);
+ gboolean from_name_matches;
+ gboolean to_name_matches;
+
+ if (buddy && buddy->alias)
+ their_name = buddy->alias;
+
+ if (log->account->alias)
+ {
+ alias = log->account->alias;
+ alias_length = strlen(alias);
+ }
+ else
+ {
+ alias = "";
+ alias_length = 0;
+ }
+
+ /* Try to guess which user is me.
+ * The first step is to determine if either of the names matches either my
+ * friendly name or alias. For this test, "match" is defined as:
+ * ^(friendly_name|alias)([^a-zA-Z0-9].*)?$
+ */
+ from_name_matches = (gaim_str_has_prefix(from_name, friendly_name) &&
+ !isalnum(*(from_name + friendly_name_length))) ||
+ (gaim_str_has_prefix(from_name, alias) &&
+ !isalnum(*(from_name + alias_length)));
+
+ to_name_matches = to_name != NULL && (
+ (gaim_str_has_prefix(to_name, friendly_name) &&
+ !isalnum(*(to_name + friendly_name_length))) ||
+ (gaim_str_has_prefix(to_name, alias) &&
+ !isalnum(*(to_name + alias_length))));
+
+ if (from_name_matches) {
+ if (!to_name_matches) {
+ name_guessed = NAME_GUESS_ME;
+ }
+ } else if (to_name_matches) {
+ name_guessed = NAME_GUESS_THEM;
+ } else {
+ if (buddy && buddy->alias) {
+ char *alias = g_strdup(buddy->alias);
+
+ /* "Truncate" the string at the first non-alphanumeric
+ * character. The idea is to relax the comparison.
+ */
+ char *temp;
+ for (temp = alias; *temp ; temp++) {
+ if (!isalnum(*temp)) {
+ *temp = '\0';
+ break;
+ }
+ }
+ alias_length = strlen(alias);
+
+ /* Try to guess which user is them.
+ * The first step is to determine if either of the names
+ * matches their alias. For this test, "match" is
+ * defined as: ^alias([^a-zA-Z0-9].*)?$
+ */
+ from_name_matches = (gaim_str_has_prefix(
+ from_name, alias) &&
+ !isalnum(*(from_name +
+ alias_length)));
+
+ to_name_matches = to_name && (gaim_str_has_prefix(
+ to_name, alias) &&
+ !isalnum(*(to_name +
+ alias_length)));
+
+ g_free(alias);
+
+ if (from_name_matches) {
+ if (!to_name_matches) {
+ name_guessed = NAME_GUESS_THEM;
+ }
+ } else if (to_name_matches) {
+ name_guessed = NAME_GUESS_ME;
+ } else if (buddy->server_alias) {
+ friendly_name_length =
+ strlen(buddy->server_alias);
+
+ /* Try to guess which user is them.
+ * The first step is to determine if either of
+ * the names matches their friendly name. For
+ * this test, "match" is defined as:
+ * ^friendly_name([^a-zA-Z0-9].*)?$
+ */
+ from_name_matches = (gaim_str_has_prefix(
+ from_name,
+ buddy->server_alias) &&
+ !isalnum(*(from_name +
+ friendly_name_length)));
+
+ to_name_matches = to_name && (
+ (gaim_str_has_prefix(
+ to_name, buddy->server_alias) &&
+ !isalnum(*(to_name +
+ friendly_name_length))));
+
+ if (from_name_matches) {
+ if (!to_name_matches) {
+ name_guessed = NAME_GUESS_THEM;
+ }
+ } else if (to_name_matches) {
+ name_guessed = NAME_GUESS_ME;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (name_guessed != NAME_GUESS_UNKNOWN) {
+ text = g_string_append(text, "<span style=\"color: #");
+ if (name_guessed == NAME_GUESS_ME)
+ text = g_string_append(text, "16569E");
+ else
+ text = g_string_append(text, "A82F2F");
+ text = g_string_append(text, ";\">");
+ }
+
+ time_unix = msn_logger_parse_timestamp(message, &tm);
+
+ timestamp = g_strdup_printf("<font size=\"2\">(%02u:%02u:%02u)</font> ",
+ tm->tm_hour, tm->tm_min, tm->tm_sec);
+ text = g_string_append(text, timestamp);
+ g_free(timestamp);
+
+ if (from_name) {
+ text = g_string_append(text, "<b>");
+
+ if (name_guessed == NAME_GUESS_ME) {
+ if (log->account->alias)
+ text = g_string_append(text, log->account->alias);
+ else
+ text = g_string_append(text, log->account->username);
+ }
+ else if (name_guessed == NAME_GUESS_THEM)
+ text = g_string_append(text, their_name);
+ else
+ text = g_string_append(text, from_name);
+
+ text = g_string_append(text, ":</b> ");
+ }
+
+ if (name_guessed != NAME_GUESS_UNKNOWN)
+ text = g_string_append(text, "</span>");
+
+ style = xmlnode_get_attrib(text_node, "Style");
+
+ tmp = xmlnode_get_data(text_node);
+ if (style && *style) {
+ text = g_string_append(text, "<span style=\"");
+ text = g_string_append(text, style);
+ text = g_string_append(text, "\">");
+ text = g_string_append(text, tmp);
+ text = g_string_append(text, "</span>\n");
+ } else {
+ text = g_string_append(text, tmp);
+ text = g_string_append(text, "\n");
+ }
+ g_free(tmp);
+ }
+
+ data->text = text;
+
+ return text->str;
+}
+
+static int msn_logger_size (GaimLog *log)
+{
+ char *text;
+ size_t size;
+
+ g_return_val_if_fail(log != NULL, 0);
+
+ if (gaim_prefs_get_bool("/plugins/core/log_reader/fast_sizes"))
+ return 0;
+
+ text = msn_logger_read(log, NULL);
+ size = strlen(text);
+ g_free(text);
+
+ return size;
+}
+
+static void msn_logger_finalize(GaimLog *log)
+{
+ struct msn_logger_data *data;
+
+ g_return_if_fail(log != NULL);
+
+ data = log->logger_data;
+
+ if (data->last_log)
+ xmlnode_free(data->root);
+
+ if (data->text)
+ g_string_free(data->text, FALSE);
+}
+
+
+/*****************************************************************************
+ * Trillian Logger *
+ *****************************************************************************/
+
+/* The trillian logger doesn't write logs, only reads them. This is to include
+ * Trillian logs in the log viewer transparently.
+ */
+
+static GaimLogLogger *trillian_logger;
+static void trillian_logger_finalize(GaimLog *log);
+
+struct trillian_logger_data {
+ char *path; /* FIXME: Change this to use GaimStringref like log.c:old_logger_list */
+ int offset;
+ int length;
+ char *their_nickname;
+};
+
+static GList *trillian_logger_list(GaimLogType type, const char *sn, GaimAccount *account)
+{
+ GList *list = NULL;
+ const char *logdir;
+ GaimPlugin *plugin;
+ GaimPluginProtocolInfo *prpl_info;
+ char *prpl_name;
+ const char *buddy_name;
+ char *filename;
+ char *path;
+ GError *error = NULL;
+ gchar *contents = NULL;
+ gsize length;
+ gchar *line;
+ gchar *c;
+
+ g_return_val_if_fail(sn != NULL, list);
+ g_return_val_if_fail(account != NULL, list);
+
+ logdir = gaim_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;
+
+ plugin = gaim_find_prpl(gaim_account_get_protocol_id(account));
+ if (!plugin)
+ return NULL;
+
+ prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(plugin);
+ if (!prpl_info->list_icon)
+ return NULL;
+
+ prpl_name = g_ascii_strup(prpl_info->list_icon(account, NULL), -1);
+
+ buddy_name = gaim_normalize(account, sn);
+
+ filename = g_strdup_printf("%s.log", buddy_name);
+ path = g_build_filename(
+ logdir, prpl_name, filename, NULL);
+
+ gaim_debug(GAIM_DEBUG_INFO, "Trillian log list",
+ "Reading %s\n", path);
+ /* FIXME: There's really no need to read the entire file at once.
+ * See src/log.c:old_logger_list for a better approach.
+ */
+ if (!g_file_get_contents(path, &contents, &length, &error)) {
+ if (error) {
+ g_error_free(error);
+ error = NULL;
+ }
+ g_free(path);
+
+ path = g_build_filename(
+ logdir, prpl_name, "Query", filename, NULL);
+ gaim_debug(GAIM_DEBUG_INFO, "Trillian log list",
+ "Reading %s\n", path);
+ if (!g_file_get_contents(path, &contents, &length, &error)) {
+ if (error)
+ g_error_free(error);
+ }
+ }
+ g_free(filename);
+
+ if (contents) {
+ struct trillian_logger_data *data = NULL;
+ int offset = 0;
+ int last_line_offset = 0;
+
+ line = contents;
+ c = contents;
+ while (*c) {
+ offset++;
+
+ if (*c != '\n') {
+ c++;
+ continue;
+ }
+
+ *c = '\0';
+ if (gaim_str_has_prefix(line, "Session Close ")) {
+ if (data && !data->length) {
+ if (!(data->length = last_line_offset - data->offset)) {
+ /* This log had no data, so we remove it. */
+ GList *last = g_list_last(list);
+
+ gaim_debug(GAIM_DEBUG_INFO, "Trillian log list",
+ "Empty log. Offset %i\n", data->offset);
+
+ trillian_logger_finalize((GaimLog *)last->data);
+ list = g_list_delete_link(list, last);
+ }
+ }
+ } else if (line[0] && line[1] && line [3] &&
+ gaim_str_has_prefix(&line[3], "sion Start ")) {
+
+ char *their_nickname = line;
+ char *timestamp;
+
+ if (data && !data->length)
+ data->length = last_line_offset - data->offset;
+
+ while (*their_nickname && (*their_nickname != ':'))
+ their_nickname++;
+ their_nickname++;
+
+ /* This code actually has nothing to do with
+ * the timestamp YET. I'm simply using this
+ * variable for now to NUL-terminate the
+ * their_nickname string.
+ */
+ timestamp = their_nickname;
+ while (*timestamp && *timestamp != ')')
+ timestamp++;
+
+ if (*timestamp == ')') {
+ char *month;
+ struct tm tm;
+
+ *timestamp = '\0';
+ if (line[0] && line[1] && line[2])
+ timestamp += 3;
+
+ /* Now we start dealing with the timestamp. */
+
+ /* Skip over the day name. */
+ while (*timestamp && (*timestamp != ' '))
+ timestamp++;
+ *timestamp = '\0';
+ timestamp++;
+
+ /* Parse out the month. */
+ month = timestamp;
+ while (*timestamp && (*timestamp != ' '))
+ timestamp++;
+ *timestamp = '\0';
+ timestamp++;
+
+ /* Parse the day, time, and year. */
+ if (sscanf(timestamp, "%u %u:%u:%u %u",
+ &tm.tm_mday, &tm.tm_hour,
+ &tm.tm_min, &tm.tm_sec,
+ &tm.tm_year) != 5) {
+
+ gaim_debug(GAIM_DEBUG_ERROR,
+ "Trillian log timestamp parse",
+ "Session Start parsing error\n");
+ } else {
+ GaimLog *log;
+
+ tm.tm_year -= 1900;
+
+ /* Let the C library deal with
+ * daylight savings time.
+ */
+ tm.tm_isdst = -1;
+
+ /* Ugly hack, in case current locale
+ * is not English. This code is taken
+ * from log.c.
+ */
+ if (strcmp(month, "Jan") == 0) {
+ tm.tm_mon= 0;
+ } else if (strcmp(month, "Feb") == 0) {
+ tm.tm_mon = 1;
+ } else if (strcmp(month, "Mar") == 0) {
+ tm.tm_mon = 2;
+ } else if (strcmp(month, "Apr") == 0) {
+ tm.tm_mon = 3;
+ } else if (strcmp(month, "May") == 0) {
+ tm.tm_mon = 4;
+ } else if (strcmp(month, "Jun") == 0) {
+ tm.tm_mon = 5;
+ } else if (strcmp(month, "Jul") == 0) {
+ tm.tm_mon = 6;
+ } else if (strcmp(month, "Aug") == 0) {
+ tm.tm_mon = 7;
+ } else if (strcmp(month, "Sep") == 0) {
+ tm.tm_mon = 8;
+ } else if (strcmp(month, "Oct") == 0) {
+ tm.tm_mon = 9;
+ } else if (strcmp(month, "Nov") == 0) {
+ tm.tm_mon = 10;
+ } else if (strcmp(month, "Dec") == 0) {
+ tm.tm_mon = 11;
+ }
+
+ data = g_new0(
+ struct trillian_logger_data, 1);
+ data->path = g_strdup(path);
+ data->offset = offset;
+ data->length = 0;
+ data->their_nickname =
+ g_strdup(their_nickname);
+
+ /* XXX: Look into this later... Should we pass in a struct tm? */
+ log = gaim_log_new(GAIM_LOG_IM,
+ sn, account, NULL, mktime(&tm), NULL);
+ log->logger = trillian_logger;
+ log->logger_data = data;
+
+ list = g_list_append(list, log);
+ }
+ }
+ }
+ c++;
+ line = c;
+ last_line_offset = offset;
+ }
+
+ g_free(contents);
+ }
+ g_free(path);
+
+ g_free(prpl_name);
+
+ return list;
+}
+
+static char * trillian_logger_read (GaimLog *log, GaimLogReadFlags *flags)
+{
+ struct trillian_logger_data *data;
+ char *read;
+ FILE *file;
+ GaimBuddy *buddy;
+ char *escaped;
+ GString *formatted;
+ char *c;
+ char *line;
+
+ g_return_val_if_fail(log != NULL, g_strdup(""));
+
+ data = log->logger_data;
+
+ g_return_val_if_fail(data->path != NULL, g_strdup(""));
+ g_return_val_if_fail(data->length > 0, g_strdup(""));
+ g_return_val_if_fail(data->their_nickname != NULL, g_strdup(""));
+
+ gaim_debug(GAIM_DEBUG_INFO, "Trillian log read",
+ "Reading %s\n", data->path);
+
+ read = g_malloc(data->length + 2);
+
+ file = g_fopen(data->path, "rb");
+ fseek(file, data->offset, SEEK_SET);
+ fread(read, data->length, 1, file);
+ fclose(file);
+
+ if (read[data->length-1] == '\n') {
+ read[data->length] = '\0';
+ } else {
+ read[data->length] = '\n';
+ read[data->length+1] = '\0';
+ }
+
+ /* Load miscellaneous data. */
+ buddy = gaim_find_buddy(log->account, log->name);
+
+ escaped = g_markup_escape_text(read, -1);
+ g_free(read);
+ read = escaped;
+
+ /* Apply formatting... */
+ formatted = g_string_new("");
+ c = read;
+ line = read;
+ while (*c)
+ {
+ if (*c == '\n')
+ {
+ char *link_temp_line;
+ char *link;
+ char *timestamp;
+ char *footer = NULL;
+ *c = '\0';
+
+ /* Convert links.
+ *
+ * The format is (Link: URL)URL
+ * So, I want to find each occurance of "(Link: " and replace that chunk with:
+ * <a href="
+ * Then, replace the next ")" with:
+ * ">
+ * Then, replace the next " " (or add this if the end-of-line is reached) with:
+ * </a>
+ */
+ link_temp_line = NULL;
+ while ((link = g_strstr_len(line, strlen(line), "(Link: "))) {
+ GString *temp;
+
+ if (!*link)
+ continue;
+
+ *link = '\0';
+ link++;
+
+ temp = g_string_new(line);
+ g_string_append(temp, "<a href=\"");
+
+ if (strlen(link) >= 6) {
+ link += (sizeof("(Link: ") - 1);
+
+ while (*link && *link != ')') {
+ g_string_append_c(temp, *link);
+ link++;
+ }
+ if (link) {
+ link++;
+
+ g_string_append(temp, "\">");
+ while (*link && *link != ' ') {
+ g_string_append_c(temp, *link);
+ link++;
+ }
+ g_string_append(temp, "</a>");
+ }
+
+ g_string_append(temp, link);
+
+ /* Free the last round's line. */
+ if (link_temp_line)
+ g_free(line);
+
+ line = temp->str;
+ g_string_free(temp, FALSE);
+
+ /* Save this memory location so we can free it later. */
+ link_temp_line = line;
+ }
+ }
+
+ timestamp = "";
+ if (*line == '[') {
+ timestamp = line;
+ while (*timestamp && *timestamp != ']')
+ timestamp++;
+ if (*timestamp == ']') {
+ *timestamp = '\0';
+ line++;
+ /* TODO: Parse the timestamp and convert it to Gaim's format. */
+ g_string_append_printf(formatted,
+ "<font size=\"2\">(%s)</font> ", line);
+ line = timestamp;
+ if (line[1] && line[2])
+ line += 2;
+ }
+
+ if (gaim_str_has_prefix(line, "*** ")) {
+ line += (sizeof("*** ") - 1);
+ g_string_append(formatted, "<b>");
+ footer = "</b>";
+ if (gaim_str_has_prefix(line, "NOTE: This user is offline.")) {
+ line = _("User is offline.");
+ } else if (gaim_str_has_prefix(line,
+ "NOTE: Your status is currently set to ")) {
+
+ line += (sizeof("NOTE: ") - 1);
+ } else if (gaim_str_has_prefix(line, "Auto-response sent to ")) {
+ g_string_append(formatted, _("Auto-response sent:"));
+ while (*line && *line != ':')
+ line++;
+ if (*line)
+ line++;
+ g_string_append(formatted, "</b>");
+ footer = NULL;
+ } else if (strstr(line, " signed off ")) {
+ if (buddy != NULL && buddy->alias)
+ g_string_append_printf(formatted,
+ _("%s has signed off."), buddy->alias);
+ else
+ g_string_append_printf(formatted,
+ _("%s has signed off."), log->name);
+ line = "";
+ } else if (strstr(line, " signed on ")) {
+ if (buddy != NULL && buddy->alias)
+ g_string_append(formatted, buddy->alias);
+ else
+ g_string_append(formatted, log->name);
+ line = " logged in.";
+ } else if (gaim_str_has_prefix(line,
+ "One or more messages may have been undeliverable.")) {
+
+ g_string_append(formatted,
+ "<span style=\"color: #ff0000;\">");
+ g_string_append(formatted,
+ _("One or more messages may have been "
+ "undeliverable."));
+ line = "";
+ footer = "</span></b>";
+ } else if (gaim_str_has_prefix(line,
+ "You have been disconnected.")) {
+
+ g_string_append(formatted,
+ "<span style=\"color: #ff0000;\">");
+ g_string_append(formatted,
+ _("You were disconnected from the server."));
+ line = "";
+ footer = "</span></b>";
+ } else if (gaim_str_has_prefix(line,
+ "You are currently disconnected.")) {
+
+ g_string_append(formatted,
+ "<span style=\"color: #ff0000;\">");
+ line = _("You are currently disconnected. Messages "
+ "will not be received unless you are "
+ "logged in.");
+ footer = "</span></b>";
+ } else if (gaim_str_has_prefix(line,
+ "Your previous message has not been sent.")) {
+
+ g_string_append(formatted,
+ "<span style=\"color: #ff0000;\">");
+
+ if (gaim_str_has_prefix(line,
+ "Your previous message has not been sent. "
+ "Reason: Maximum length exceeded.")) {
+
+ g_string_append(formatted,
+ _("Message could not be sent because "
+ "the maximum length was exceeded."));
+ line = "";
+ } else {
+ g_string_append(formatted,
+ _("Message could not be sent."));
+ line += (sizeof(
+ "Your previous message "
+ "has not been sent. ") - 1);
+ }
+
+ footer = "</span></b>";
+ }
+ } else if (gaim_str_has_prefix(line, data->their_nickname)) {
+ if (buddy != NULL && buddy->alias) {
+ line += strlen(data->their_nickname) + 2;
+ g_string_append_printf(formatted,
+ "<span style=\"color: #A82F2F;\">"
+ "<b>%s</b></span>: ", buddy->alias);
+ }
+ } else {
+ char *line2 = line;
+ while (*line2 && *line2 != ':')
+ line2++;
+ if (*line2 == ':') {
+ const char *acct_name;
+ line2++;
+ line = line2;
+ acct_name = gaim_account_get_alias(log->account);
+ if (!acct_name)
+ acct_name = gaim_account_get_username(log->account);
+
+ g_string_append_printf(formatted,
+ "<span style=\"color: #16569E;\">"
+ "<b>%s</b></span>:", acct_name);
+ }
+ }
+ }
+
+ g_string_append(formatted, line);
+
+ if (footer)
+ g_string_append(formatted, footer);
+
+ g_string_append_c(formatted, '\n');
+
+ if (link_temp_line)
+ g_free(link_temp_line);
+
+ c++;
+ line = c;
+ } else
+ c++;
+ }
+
+ g_free(read);
+ read = formatted->str;
+ g_string_free(formatted, FALSE);
+
+ return read;
+}
+
+static int trillian_logger_size (GaimLog *log)
+{
+ struct trillian_logger_data *data;
+ char *text;
+ size_t size;
+
+ g_return_val_if_fail(log != NULL, 0);
+
+ data = log->logger_data;
+
+ if (gaim_prefs_get_bool("/plugins/core/log_reader/fast_sizes")) {
+ return data ? data->length : 0;
+ }
+
+ text = trillian_logger_read(log, NULL);
+ size = strlen(text);
+ g_free(text);
+
+ return size;
+}
+
+static void trillian_logger_finalize(GaimLog *log)
+{
+ struct trillian_logger_data *data;
+
+ g_return_if_fail(log != NULL);
+
+ data = log->logger_data;
+
+ g_free(data->path);
+ g_free(data->their_nickname);
+
+}
+
+
+/*****************************************************************************
+ * Plugin Code *
+ *****************************************************************************/
+
+static void
+init_plugin(GaimPlugin *plugin)
+{
+ char *path;
+#ifdef _WIN32
+ char *folder;
+ gboolean found = FALSE;
+#endif
+
+ g_return_if_fail(plugin != NULL);
+
+ gaim_prefs_add_none("/plugins/core/log_reader");
+
+
+ /* Add general preferences. */
+
+ gaim_prefs_add_bool("/plugins/core/log_reader/fast_sizes", FALSE);
+ gaim_prefs_add_bool("/plugins/core/log_reader/use_name_heuristics", TRUE);
+
+
+ /* Add Adium log directory preference. */
+ gaim_prefs_add_none("/plugins/core/log_reader/adium");
+
+ /* Calculate default Adium log directory. */
+#ifdef _WIN32
+ path = "";
+#else
+ path = g_build_filename(gaim_home_dir(), "Library", "Application Support",
+ "Adium 2.0", "Users", "Default", "Logs", NULL);
+#endif
+
+ gaim_prefs_add_string("/plugins/core/log_reader/adium/log_directory", path);
+
+#ifndef _WIN32
+ g_free(path);
+#endif
+
+
+ /* Add Fire log directory preference. */
+ gaim_prefs_add_none("/plugins/core/log_reader/fire");
+
+ /* Calculate default Fire log directory. */
+#ifdef _WIN32
+ path = "";
+#else
+ path = g_build_filename(gaim_home_dir(), "Library", "Application Support",
+ "Fire", "Sessions", NULL);
+#endif
+
+ gaim_prefs_add_string("/plugins/core/log_reader/fire/log_directory", path);
+
+#ifndef _WIN32
+ g_free(path);
+#endif
+
+
+ /* Add Messenger Plus! log directory preference. */
+ gaim_prefs_add_none("/plugins/core/log_reader/messenger_plus");
+
+ /* Calculate default Messenger Plus! log directory. */
+#ifdef _WIN32
+ folder = wgaim_get_special_folder(CSIDL_PERSONAL);
+ if (folder) {
+#endif
+ path = g_build_filename(
+#ifdef _WIN32
+ folder,
+#else
+ GAIM_LOG_READER_WINDOWS_MOUNT_POINT, "Documents and Settings",
+ g_get_user_name(), "My Documents",
+#endif
+ "My Chat Logs", NULL);
+#ifdef _WIN32
+ g_free(folder);
+ } else /* !folder */
+ path = g_strdup("");
+#endif
+
+ gaim_prefs_add_string("/plugins/core/log_reader/messenger_plus/log_directory", path);
+ g_free(path);
+
+
+ /* Add MSN Messenger log directory preference. */
+ gaim_prefs_add_none("/plugins/core/log_reader/msn");
+
+ /* Calculate default MSN message history directory. */
+#ifdef _WIN32
+ folder = wgaim_get_special_folder(CSIDL_PERSONAL);
+ if (folder) {
+#endif
+ path = g_build_filename(
+#ifdef _WIN32
+ folder,
+#else
+ GAIM_LOG_READER_WINDOWS_MOUNT_POINT, "Documents and Settings",
+ g_get_user_name(), "My Documents",
+#endif
+ "My Received Files", NULL);
+#ifdef _WIN32
+ g_free(folder);
+ } else /* !folder */
+ path = g_strdup("");
+#endif
+
+ gaim_prefs_add_string("/plugins/core/log_reader/msn/log_directory", path);
+ g_free(path);
+
+
+ /* Add Trillian log directory preference. */
+ gaim_prefs_add_none("/plugins/core/log_reader/trillian");
+
+#ifdef _WIN32
+ /* XXX: While a major hack, this is the most reliable way I could
+ * think of to determine the Trillian installation directory.
+ */
+
+ path = NULL;
+ if ((folder = wgaim_read_reg_string(HKEY_CLASSES_ROOT, "Trillian.SkinZip\\shell\\Add\\command\\", NULL))) {
+ char *value = folder;
+ char *temp;
+
+ /* Break apart buffer. */
+ if (*value == '"') {
+ value++;
+ temp = value;
+ while (*temp && *temp != '"')
+ temp++;
+ } else {
+ temp = value;
+ while (*temp && *temp != ' ')
+ temp++;
+ }
+ *temp = '\0';
+
+ /* Set path. */
+ if (gaim_str_has_suffix(value, "trillian.exe")) {
+ value[strlen(value) - (sizeof("trillian.exe") - 1)] = '\0';
+ path = g_build_filename(value, "users", "default", "talk.ini", NULL);
+ }
+ g_free(folder);
+ }
+
+ if (!path) {
+ char *folder = wgaim_get_special_folder(CSIDL_PROGRAM_FILES);
+ if (folder) {
+ path = g_build_filename(folder, "Trillian",
+ "users", "default", "talk.ini", NULL);
+ g_free(folder);
+ }
+ }
+
+ if (path) {
+ /* Read talk.ini file to find the log directory. */
+ GError *error = NULL;
+
+#if 0 && GLIB_CHECK_VERSION(2,6,0) /* FIXME: Not tested yet. */
+ GKeyFile *key_file;
+
+ gaim_debug(GAIM_DEBUG_INFO, "Trillian talk.ini read",
+ "Reading %s\n", path);
+ if (!g_key_file_load_from_file(key_file, path, G_KEY_FILE_NONE, GError &error)) {
+ gaim_debug(GAIM_DEBUG_ERROR, "Trillian talk.ini read",
+ "Error reading talk.ini\n");
+ if (error)
+ g_error_free(error);
+ } else {
+ char *logdir = g_key_file_get_string(key_file, "Logging", "Directory", &error);
+ if (error) {
+ gaim_debug(GAIM_DEBUG_ERROR, "Trillian talk.ini read",
+ "Error reading Directory value from Logging section\n");
+ g_error_free(error);
+ }
+
+ if (logdir) {
+ g_strchomp(logdir);
+ gaim_prefs_add_string(
+ "/plugins/core/log_reader/trillian/log_directory", logdir);
+ found = TRUE;
+ }
+
+ g_key_file_free(key_file);
+ }
+#else /* !GLIB_CHECK_VERSION(2,6,0) */
+ gsize length;
+ gchar *contents = NULL;
+
+ gaim_debug(GAIM_DEBUG_INFO, "Trillian talk.ini read",
+ "Reading %s\n", path);
+ if (!g_file_get_contents(path, &contents, &length, &error)) {
+ gaim_debug(GAIM_DEBUG_ERROR, "Trillian talk.ini read",
+ "Error reading talk.ini\n");
+ if (error)
+ g_error_free(error);
+ } else {
+ char *line = contents;
+ while (*contents) {
+ if (*contents == '\n') {
+ *contents = '\0';
+
+ /* XXX: This assumes the first Directory key is under [Logging]. */
+ if (gaim_str_has_prefix(line, "Directory=")) {
+ line += (sizeof("Directory=") - 1);
+ g_strchomp(line);
+ gaim_prefs_add_string(
+ "/plugins/core/log_reader/trillian/log_directory",
+ line);
+ found = TRUE;
+ }
+
+ contents++;
+ line = contents;
+ } else
+ contents++;
+ }
+ g_free(path);
+ g_free(contents);
+ }
+#endif /* !GTK_CHECK_VERSION(2,6,0) */
+ } /* path */
+
+ if (!found) {
+#endif /* defined(_WIN32) */
+
+ /* Calculate default Trillian log directory. */
+#ifdef _WIN32
+ folder = wgaim_get_special_folder(CSIDL_PROGRAM_FILES);
+ if (folder) {
+#endif
+ path = g_build_filename(
+#ifdef _WIN32
+ folder,
+#else
+ GAIM_LOG_READER_WINDOWS_MOUNT_POINT, "Program Files",
+#endif
+ "Trillian", "users", "default", "logs", NULL);
+#ifdef _WIN32
+ g_free(folder);
+ } else /* !folder */
+ path = g_strdup("");
+#endif
+
+ gaim_prefs_add_string("/plugins/core/log_reader/trillian/log_directory", path);
+ g_free(path);
+
+#ifdef _WIN32
+ } /* !found */
+#endif
+}
+
+static gboolean
+plugin_load(GaimPlugin *plugin)
+{
+ g_return_val_if_fail(plugin != NULL, FALSE);
+
+ /* The names of IM clients are marked for translation at the request of
+ translators who wanted to transliterate them. Many translators
+ choose to leave them alone. Choose what's best for your language. */
+ adium_logger = gaim_log_logger_new("adium", _("Adium"), 6,
+ NULL,
+ NULL,
+ adium_logger_finalize,
+ adium_logger_list,
+ adium_logger_read,
+ adium_logger_size);
+ gaim_log_logger_add(adium_logger);
+
+#if 0
+ /* The names of IM clients are marked for translation at the request of
+ translators who wanted to transliterate them. Many translators
+ choose to leave them alone. Choose what's best for your language. */
+ fire_logger = gaim_log_logger_new("fire", _("Fire"), 6,
+ NULL,
+ NULL,
+ fire_logger_finalize,
+ fire_logger_list,
+ fire_logger_read,
+ fire_logger_size);
+ gaim_log_logger_add(fire_logger);
+
+ /* The names of IM clients are marked for translation at the request of
+ translators who wanted to transliterate them. Many translators
+ choose to leave them alone. Choose what's best for your language. */
+ messenger_plus_logger = gaim_log_logger_new("messenger_plus", _("Messenger Plus!"), 6,
+ NULL,
+ NULL,
+ messenger_plus_logger_finalize,
+ messenger_plus_logger_list,
+ messenger_plus_logger_read,
+ messenger_plus_logger_size);
+ gaim_log_logger_add(messenger_plus_logger);
+#endif
+
+ /* The names of IM clients are marked for translation at the request of
+ translators who wanted to transliterate them. Many translators
+ choose to leave them alone. Choose what's best for your language. */
+ msn_logger = gaim_log_logger_new("msn", _("MSN Messenger"), 6,
+ NULL,
+ NULL,
+ msn_logger_finalize,
+ msn_logger_list,
+ msn_logger_read,
+ msn_logger_size);
+ gaim_log_logger_add(msn_logger);
+
+ /* The names of IM clients are marked for translation at the request of
+ translators who wanted to transliterate them. Many translators
+ choose to leave them alone. Choose what's best for your language. */
+ trillian_logger = gaim_log_logger_new("trillian", _("Trillian"), 6,
+ NULL,
+ NULL,
+ trillian_logger_finalize,
+ trillian_logger_list,
+ trillian_logger_read,
+ trillian_logger_size);
+ gaim_log_logger_add(trillian_logger);
+
+ return TRUE;
+}
+
+static gboolean
+plugin_unload(GaimPlugin *plugin)
+{
+ g_return_val_if_fail(plugin != NULL, FALSE);
+
+ gaim_log_logger_remove(adium_logger);
+#if 0
+ gaim_log_logger_remove(fire_logger);
+ gaim_log_logger_remove(messenger_plus_logger);
+#endif
+ gaim_log_logger_remove(msn_logger);
+ gaim_log_logger_remove(trillian_logger);
+
+ return TRUE;
+}
+
+static GaimPluginPrefFrame *
+get_plugin_pref_frame(GaimPlugin *plugin)
+{
+ GaimPluginPrefFrame *frame;
+ GaimPluginPref *ppref;
+
+ g_return_val_if_fail(plugin != NULL, FALSE);
+
+ frame = gaim_plugin_pref_frame_new();
+
+
+ /* Add general preferences. */
+
+ ppref = gaim_plugin_pref_new_with_label(_("General Log Reading Configuration"));
+ gaim_plugin_pref_frame_add(frame, ppref);
+
+ ppref = gaim_plugin_pref_new_with_name_and_label(
+ "/plugins/core/log_reader/fast_sizes", _("Fast size calculations"));
+ gaim_plugin_pref_frame_add(frame, ppref);
+
+ ppref = gaim_plugin_pref_new_with_name_and_label(
+ "/plugins/core/log_reader/use_name_heuristics", _("Use name heuristics"));
+ gaim_plugin_pref_frame_add(frame, ppref);
+
+
+ /* Add Log Directory preferences. */
+
+ ppref = gaim_plugin_pref_new_with_label(_("Log Directory"));
+ gaim_plugin_pref_frame_add(frame, ppref);
+
+ ppref = gaim_plugin_pref_new_with_name_and_label(
+ "/plugins/core/log_reader/adium/log_directory", _("Adium"));
+ gaim_plugin_pref_frame_add(frame, ppref);
+
+#if 0
+ ppref = gaim_plugin_pref_new_with_name_and_label(
+ "/plugins/core/log_reader/fire/log_directory", _("Fire"));
+ gaim_plugin_pref_frame_add(frame, ppref);
+
+ ppref = gaim_plugin_pref_new_with_name_and_label(
+ "/plugins/core/log_reader/messenger_plus/log_directory", _("Messenger Plus!"));
+ gaim_plugin_pref_frame_add(frame, ppref);
+#endif
+
+ ppref = gaim_plugin_pref_new_with_name_and_label(
+ "/plugins/core/log_reader/msn/log_directory", _("MSN Messenger"));
+ gaim_plugin_pref_frame_add(frame, ppref);
+
+ ppref = gaim_plugin_pref_new_with_name_and_label(
+ "/plugins/core/log_reader/trillian/log_directory", _("Trillian"));
+ gaim_plugin_pref_frame_add(frame, ppref);
+
+ return frame;
+}
+
+static GaimPluginUiInfo prefs_info = {
+ get_plugin_pref_frame,
+ 0, /* page_num (reserved) */
+ NULL /* frame (reserved) */
+};
+
+static GaimPluginInfo info =
+{
+ GAIM_PLUGIN_MAGIC,
+ GAIM_MAJOR_VERSION,
+ GAIM_MINOR_VERSION,
+ GAIM_PLUGIN_STANDARD, /**< type */
+ NULL, /**< ui_requirement */
+ 0, /**< flags */
+ NULL, /**< dependencies */
+ GAIM_PRIORITY_DEFAULT, /**< priority */
+ "core-log_reader", /**< id */
+ N_("Log Reader"), /**< name */
+ VERSION, /**< version */
+
+ /** summary */
+ N_("Includes other IM clients' logs in the "
+ "log viewer."),
+
+ /** description */
+ N_("When viewing logs, this plugin will include "
+ "logs from other IM clients. Currently, this "
+ "includes Adium, MSN Messenger, and Trillian.\n\n"
+ "WARNING: This plugin is still alpha code and "
+ "may crash frequently. Use it at your own risk!"),
+
+ "Richard Laager <rlaager@users.sf.net>", /**< author */
+ GAIM_WEBSITE, /**< homepage */
+ plugin_load, /**< load */
+ plugin_unload, /**< unload */
+ NULL, /**< destroy */
+ NULL, /**< ui_info */
+ NULL, /**< extra_info */
+ &prefs_info, /**< prefs_info */
+ NULL /**< actions */
+};
+
+GAIM_INIT_PLUGIN(log_reader, init_plugin, info)
diff --git a/libpurple/plugins/mono/BooPlugin.boo b/libpurple/plugins/mono/BooPlugin.boo
new file mode 100644
index 0000000000..bae8579f74
--- /dev/null
+++ b/libpurple/plugins/mono/BooPlugin.boo
@@ -0,0 +1,22 @@
+import Gaim
+
+class BooPlugin(GaimPlugin):
+
+ def handle(*args as (object)):
+ b as Buddy
+ b = args[0]
+ Debug.debug(Debug.INFO, "booplugin", "Boo Plugin knows that " + b.Alias + " is away\n")
+
+ override def Load():
+ Debug.debug(Debug.INFO, "booplugin", "loading...\n")
+ BuddyList.OnBuddyAway.connect(self, handle)
+
+ override def Unload():
+ Debug.debug(Debug.INFO, "booplugin", "unloading...\n")
+
+ override def Destroy():
+ Debug.debug(Debug.INFO, "booplugin", "destroying...\n")
+
+ override def Info():
+ return GaimPluginInfo("Boo Plugin", "0.1", "Test Boo Plugin", "Longer Description", "Eoin Coffey", "urled")
+
diff --git a/libpurple/plugins/mono/GetBuddyBack.cs b/libpurple/plugins/mono/GetBuddyBack.cs
new file mode 100644
index 0000000000..09e667dd40
--- /dev/null
+++ b/libpurple/plugins/mono/GetBuddyBack.cs
@@ -0,0 +1,32 @@
+using Gaim;
+
+public class GetBuddyBack : GaimPlugin
+{
+ public void HandleSig(object[] args)
+ {
+ Buddy buddy = (Buddy)args[0];
+
+ Debug.debug(Debug.INFO, "buddyback", "buddy " + buddy.Name + " is back!\n");
+ }
+
+ public override void Load()
+ {
+ Debug.debug(Debug.INFO, "buddyback", "loading...\n");
+
+ /*Signal.connect(BuddyList.GetHandle(), this, "buddy-back", new Signal.Handler(HandleSig));*/
+ /*BuddyList.OnBuddyBack.connect(this, new Signal.Handler(HandleSig));*/
+ }
+
+ public override void Unload()
+ {
+ }
+
+ public override void Destroy()
+ {
+ }
+
+ public override GaimPluginInfo Info()
+ {
+ return new GaimPluginInfo("C# Get Buddy Back", "0.1", "Prints when a Buddy returns", "Longer Description", "Eoin Coffey", "urled");
+ }
+}
diff --git a/libpurple/plugins/mono/MPlugin.cs b/libpurple/plugins/mono/MPlugin.cs
new file mode 100644
index 0000000000..0a6762d2e4
--- /dev/null
+++ b/libpurple/plugins/mono/MPlugin.cs
@@ -0,0 +1,36 @@
+using Gaim;
+
+public class MPlugin : GaimPlugin
+{
+ public void HandleSig(object[] args)
+ {
+ Buddy buddy = (Buddy)args[0];
+ Status old_status = (Status)args[1];
+ Status status = (Status)args[2];
+
+ Debug.debug(Debug.INFO, "mplug", "buddy " + buddy.Name + " went from " + old_status.Id + " to " + status.Id + "\n");
+ }
+
+ public override void Load()
+ {
+ Debug.debug(Debug.INFO, "mplug", "loading...\n");
+
+ /*Signal.connect(BuddyList.GetHandle(), this, "buddy-away", new Signal.Handler(HandleSig));*/
+ BuddyList.OnBuddyStatusChanged.connect(this, new Signal.Handler(HandleSig));
+ }
+
+ public override void Unload()
+ {
+ Debug.debug(Debug.INFO, "mplug", "unloading...\n");
+ }
+
+ public override void Destroy()
+ {
+ Debug.debug(Debug.INFO, "mplug", "destroying...\n");
+ }
+
+ public override GaimPluginInfo Info()
+ {
+ return new GaimPluginInfo("C# Plugin", "0.1", "Test C# Plugin", "Longer Description", "Eoin Coffey", "urled");
+ }
+}
diff --git a/libpurple/plugins/mono/Makefile.am b/libpurple/plugins/mono/Makefile.am
new file mode 100644
index 0000000000..8fb0c75fcd
--- /dev/null
+++ b/libpurple/plugins/mono/Makefile.am
@@ -0,0 +1,19 @@
+SUBDIRS = api loader
+
+mono_sources = GetBuddyBack.cs \
+ MPlugin.cs
+
+EXTRA_DIST = $(mono_sources)
+
+monodir = $(libdir)/gaim
+mono_SCRIPTS = MPlugin.dll GetBuddyBack.dll
+mono_build_sources = $(addprefix $(srcdir)/, $(mono_sources))
+
+all: $(mono_SCRIPTS)
+
+SUFFIXES = .cs .dll
+.cs.dll: api/GaimAPI.dll $(mono_build_sources)
+ mcs -t:library -lib:./api -out:$@ -r:GaimAPI.dll $<
+
+clean-local:
+ rm -f $(mono_SCRIPTS)
diff --git a/libpurple/plugins/mono/api/BlistNode.cs b/libpurple/plugins/mono/api/BlistNode.cs
new file mode 100644
index 0000000000..a6108171a9
--- /dev/null
+++ b/libpurple/plugins/mono/api/BlistNode.cs
@@ -0,0 +1,4 @@
+namespace Gaim {
+ public abstract class BlistNode {
+ }
+}
diff --git a/libpurple/plugins/mono/api/Buddy.cs b/libpurple/plugins/mono/api/Buddy.cs
new file mode 100644
index 0000000000..2e1287b337
--- /dev/null
+++ b/libpurple/plugins/mono/api/Buddy.cs
@@ -0,0 +1,9 @@
+namespace Gaim {
+ public class Buddy : BlistNode {
+ private string name;
+ private string alias;
+
+ public string Name { get { return name; } set { name = value; } }
+ public string Alias { get { return alias; } set { alias = value; } }
+ }
+}
diff --git a/libpurple/plugins/mono/api/BuddyList.cs b/libpurple/plugins/mono/api/BuddyList.cs
new file mode 100644
index 0000000000..384df53d58
--- /dev/null
+++ b/libpurple/plugins/mono/api/BuddyList.cs
@@ -0,0 +1,19 @@
+namespace Gaim {
+ using System;
+ using System.Runtime.CompilerServices;
+
+ public class BuddyList {
+ [MethodImplAttribute(MethodImplOptions.InternalCall)]
+ extern private static IntPtr _get_handle();
+
+ private static IntPtr handle = _get_handle();
+
+ public static Event OnBuddyStatusChanged =
+ new Event(handle, "buddy-status-changed");
+
+ public static IntPtr GetHandle()
+ {
+ return _get_handle();
+ }
+ }
+}
diff --git a/libpurple/plugins/mono/api/Contact.cs b/libpurple/plugins/mono/api/Contact.cs
new file mode 100644
index 0000000000..627eea2319
--- /dev/null
+++ b/libpurple/plugins/mono/api/Contact.cs
@@ -0,0 +1,4 @@
+namespace Gaim {
+ public class Contact : BlistNode {
+ }
+}
diff --git a/libpurple/plugins/mono/api/Debug.cs b/libpurple/plugins/mono/api/Debug.cs
new file mode 100644
index 0000000000..fde8bb3f11
--- /dev/null
+++ b/libpurple/plugins/mono/api/Debug.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Runtime.CompilerServices;
+
+namespace Gaim
+{
+ public class Debug
+ {
+ public static int ALL = 0;
+ public static int MISC = 1;
+ public static int INFO = 2;
+ public static int WARNING = 3;
+ public static int ERROR = 4;
+ public static int FATAL = 5;
+
+ [MethodImplAttribute(MethodImplOptions.InternalCall)]
+ extern private static void _debug(int type, string cat, string str);
+
+ public static void debug(int type, string cat, string format)
+ {
+ _debug(type, cat, format);
+ }
+
+ public static void debug(int type, string cat, string format, params object[] args)
+ {
+ _debug(type, cat, String.Format(format, args));
+ }
+ }
+}
diff --git a/libpurple/plugins/mono/api/Event.cs b/libpurple/plugins/mono/api/Event.cs
new file mode 100644
index 0000000000..266ea12f37
--- /dev/null
+++ b/libpurple/plugins/mono/api/Event.cs
@@ -0,0 +1,21 @@
+using System;
+
+namespace Gaim
+{
+ public class Event
+ {
+ private IntPtr handle;
+ private string signal;
+
+ public Event(IntPtr h, string s)
+ {
+ handle = h;
+ signal = s;
+ }
+
+ public void connect(object plugin, Signal.Handler handler)
+ {
+ Signal.connect(handle, plugin, signal, handler);
+ }
+ }
+}
diff --git a/libpurple/plugins/mono/api/GaimPlugin.cs b/libpurple/plugins/mono/api/GaimPlugin.cs
new file mode 100644
index 0000000000..89739ca418
--- /dev/null
+++ b/libpurple/plugins/mono/api/GaimPlugin.cs
@@ -0,0 +1,61 @@
+namespace Gaim {
+ public class PluginInfo {
+ private string name;
+ private string version;
+ private string summary;
+ private string description;
+ private string author;
+ private string homepage;
+
+ public PluginInfo(string name, string version, string summary,
+ string description, string author, string homepage)
+ {
+ this.name = name;
+ this.version = version;
+ this.summary = summary;
+ this.description = description;
+ this.author = author;
+ this.homepage = homepage;
+ }
+
+ public string Name {
+ get { return name; }
+ }
+
+ public string Version {
+ get { return version; }
+ }
+
+ public string Summary {
+ get { return summary; }
+ }
+
+ public string Description {
+ get { return description; }
+ }
+
+ public string Author {
+ get { return author; }
+ }
+
+ public string Homepage {
+ get { return homepage; }
+ }
+ }
+
+ abstract public class Plugin {
+ private PluginInfo info;
+
+ public Plugin(PluginInfo info) {
+ this.info = info;
+ }
+
+ public abstract void Load();
+ public abstract void Unload();
+ public abstract void Destroy();
+
+ public PluginInfo Info {
+ get { return info; }
+ }
+ }
+}
diff --git a/libpurple/plugins/mono/api/Group.cs b/libpurple/plugins/mono/api/Group.cs
new file mode 100644
index 0000000000..87ce3b8230
--- /dev/null
+++ b/libpurple/plugins/mono/api/Group.cs
@@ -0,0 +1,4 @@
+namespace Gaim {
+ public class Group : BlistNode {
+ }
+}
diff --git a/libpurple/plugins/mono/api/Makefile.am b/libpurple/plugins/mono/api/Makefile.am
new file mode 100644
index 0000000000..43bc5476cd
--- /dev/null
+++ b/libpurple/plugins/mono/api/Makefile.am
@@ -0,0 +1,26 @@
+monodir=$(libdir)/gaim
+mono_sources = \
+ BlistNode.cs \
+ BuddyList.cs \
+ Buddy.cs \
+ Contact.cs \
+ Debug.cs \
+ Event.cs \
+ GaimPlugin.cs \
+ Group.cs \
+ Signal.cs \
+ Status.cs
+
+EXTRA_DIST = $(mono_sources)
+
+mono_SCRIPTS = GaimAPI.dll
+
+mono_build_sources = $(addprefix $(srcdir)/, $(mono_sources))
+
+all: $(mono_SCRIPTS)
+
+$(mono_SCRIPTS): $(mono_build_sources)
+ mcs -t:library -out:$(mono_SCRIPTS) $(mono_build_sources)
+
+clean-local:
+ rm -rf $(mono_SCRIPTS)
diff --git a/libpurple/plugins/mono/api/Signal.cs b/libpurple/plugins/mono/api/Signal.cs
new file mode 100644
index 0000000000..eeb1fab2d5
--- /dev/null
+++ b/libpurple/plugins/mono/api/Signal.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Runtime.CompilerServices;
+
+namespace Gaim
+{
+ public class Signal
+ {
+ [MethodImplAttribute(MethodImplOptions.InternalCall)]
+ extern private static int _connect(IntPtr handle, object plugin, string signal, object evnt);
+
+ public delegate void Handler(object[] args);
+
+ public static int connect(IntPtr handle, object plugin, string signal, object evnt)
+ {
+ return _connect(handle, plugin, signal, evnt);
+ }
+ }
+}
diff --git a/libpurple/plugins/mono/api/Status.cs b/libpurple/plugins/mono/api/Status.cs
new file mode 100644
index 0000000000..a000fabf95
--- /dev/null
+++ b/libpurple/plugins/mono/api/Status.cs
@@ -0,0 +1,9 @@
+namespace Gaim
+{
+ public class Status
+ {
+ private string id;
+
+ public string Id { get { return id; } set { id = value; } }
+ }
+}
diff --git a/libpurple/plugins/mono/loader/Makefile.am b/libpurple/plugins/mono/loader/Makefile.am
new file mode 100644
index 0000000000..58a57f9362
--- /dev/null
+++ b/libpurple/plugins/mono/loader/Makefile.am
@@ -0,0 +1,25 @@
+plugindir = $(libdir)/gaim
+
+plugin_LTLIBRARIES = mono.la
+
+mono_la_SOURCES = \
+ mono.c \
+ mono-glue.h \
+ mono-helper.c \
+ mono-helper.h \
+ debug-glue.c \
+ signal-glue.c \
+ blist-glue.c \
+ status-glue.c
+
+mono_la_LDFLAGS = -module -avoid-version
+
+mono_la_LIBADD = $(MONO_LIBS)
+
+AM_CPPFLAGS = \
+ -DVERSION=\"$(VERSION)\" \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/libgaim \
+ $(DEBUG_CFLAGS) \
+ $(PLUGIN_CFLAGS) \
+ $(MONO_CFLAGS)
diff --git a/libpurple/plugins/mono/loader/blist-glue.c b/libpurple/plugins/mono/loader/blist-glue.c
new file mode 100644
index 0000000000..a6673a2e95
--- /dev/null
+++ b/libpurple/plugins/mono/loader/blist-glue.c
@@ -0,0 +1,26 @@
+#include <string.h>
+#include "blist.h"
+#include "mono-helper.h"
+#include "mono-glue.h"
+
+MonoObject* gaim_blist_get_handle_glue(void)
+{
+ void *handle = gaim_blist_get_handle();
+
+ return mono_value_box(ml_get_domain(), mono_get_intptr_class(), &handle);
+}
+
+MonoObject* gaim_blist_build_buddy_object(void* data)
+{
+ MonoObject *obj = NULL;
+
+ GaimBuddy *buddy = (GaimBuddy*)data;
+
+ obj = ml_create_api_object("Buddy");
+ g_return_val_if_fail(obj != NULL, NULL);
+
+ ml_set_prop_string(obj, "Name", (char*)gaim_buddy_get_name(buddy));
+ ml_set_prop_string(obj, "Alias", (char*)gaim_buddy_get_alias(buddy));
+
+ return obj;
+}
diff --git a/libpurple/plugins/mono/loader/debug-glue.c b/libpurple/plugins/mono/loader/debug-glue.c
new file mode 100644
index 0000000000..9d5107bad3
--- /dev/null
+++ b/libpurple/plugins/mono/loader/debug-glue.c
@@ -0,0 +1,16 @@
+#include "mono-glue.h"
+#include "debug.h"
+
+void gaim_debug_glue(int type, MonoString *cat, MonoString *str)
+{
+ char *ccat;
+ char *cstr;
+
+ ccat = mono_string_to_utf8(cat);
+ cstr = mono_string_to_utf8(str);
+
+ gaim_debug(type, ccat, cstr);
+
+ g_free(ccat);
+ g_free(cstr);
+}
diff --git a/libpurple/plugins/mono/loader/mono-glue.h b/libpurple/plugins/mono/loader/mono-glue.h
new file mode 100644
index 0000000000..da0d37e988
--- /dev/null
+++ b/libpurple/plugins/mono/loader/mono-glue.h
@@ -0,0 +1,19 @@
+#ifndef _GAIM_MONO_LOADER_GLUE_H_
+#define _GAIM_MONO_LOADER_GLUE_H_
+
+#include <mono/jit/jit.h>
+#include <mono/metadata/object.h>
+#include <mono/metadata/environment.h>
+#include <mono/metadata/assembly.h>
+
+void gaim_debug_glue(int type, MonoString *cat, MonoString *str);
+
+int gaim_signal_connect_glue(MonoObject *h, MonoObject *plugin, MonoString *signal, MonoObject *func);
+
+MonoObject* gaim_blist_get_handle_glue(void);
+
+MonoObject* gaim_blist_build_buddy_object(void* buddy);
+
+MonoObject* gaim_status_build_status_object(void* data);
+
+#endif
diff --git a/libpurple/plugins/mono/loader/mono-helper.c b/libpurple/plugins/mono/loader/mono-helper.c
new file mode 100644
index 0000000000..84f6200332
--- /dev/null
+++ b/libpurple/plugins/mono/loader/mono-helper.c
@@ -0,0 +1,251 @@
+/*
+ * Mono Plugin Loader
+ *
+ * -- Thanks to the perl plugin loader for all the great tips ;-)
+ *
+ * Eoin Coffey
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib.h>
+#include <string.h>
+#include "mono-helper.h"
+#include "mono-glue.h"
+#include "value.h"
+#include "debug.h"
+
+static gboolean _runtime_active = FALSE;
+
+gboolean ml_init()
+{
+ MonoDomain *d;
+
+ g_return_val_if_fail(_runtime_active == FALSE, TRUE);
+
+ d = mono_jit_init("gaim");
+
+ if (!d) {
+ ml_set_domain(NULL);
+ return FALSE;
+ }
+
+ ml_set_domain(d);
+
+ ml_init_internal_calls();
+
+ _runtime_active = TRUE;
+
+ return TRUE;
+}
+
+void ml_uninit()
+{
+ g_return_if_fail(_runtime_active == TRUE);
+
+ mono_jit_cleanup(ml_get_domain());
+
+ ml_set_domain(NULL);
+
+ _runtime_active = FALSE;
+}
+
+MonoObject* ml_delegate_invoke(MonoObject *method, void **params)
+{
+ MonoObject *ret, *exception;
+
+ ret = mono_runtime_delegate_invoke(method, params, &exception);
+ if (exception) {
+ gaim_debug(GAIM_DEBUG_ERROR, "mono", "caught exception: %s\n", mono_class_get_name(mono_object_get_class(exception)));
+ }
+
+ return ret;
+}
+
+MonoObject* ml_invoke(MonoMethod *method, void *obj, void **params)
+{
+ MonoObject *ret, *exception;
+
+ ret = mono_runtime_invoke(method, obj, params, &exception);
+ if (exception) {
+ gaim_debug(GAIM_DEBUG_ERROR, "mono", "caught exception: %s\n", mono_class_get_name(mono_object_get_class(exception)));
+ }
+
+ return ret;
+}
+
+MonoClass* ml_find_plugin_class(MonoImage *image)
+{
+ MonoClass *klass, *pklass = NULL;
+ int i, total;
+
+ total = mono_image_get_table_rows (image, MONO_TABLE_TYPEDEF);
+ for (i = 1; i <= total; ++i) {
+ klass = mono_class_get (image, MONO_TOKEN_TYPE_DEF | i);
+ pklass = mono_class_get_parent(klass);
+ if (pklass)
+ if (strcmp("GaimPlugin", mono_class_get_name(pklass)) == 0)
+ return klass;
+ }
+
+ return NULL;
+}
+
+void ml_set_prop_string(MonoObject *obj, char *field, char *data)
+{
+ MonoClass *klass;
+ MonoProperty *prop;
+ MonoString *str;
+ gpointer args[1];
+
+ klass = mono_object_get_class(obj);
+
+ prop = mono_class_get_property_from_name(klass, field);
+
+ str = mono_string_new(ml_get_domain(), data);
+
+ args[0] = str;
+
+ mono_property_set_value(prop, obj, args, NULL);
+}
+
+gchar* ml_get_prop_string(MonoObject *obj, char *field)
+{
+ MonoClass *klass;
+ MonoProperty *prop;
+ MonoString *str;
+
+ klass = mono_object_get_class(obj);
+
+ prop = mono_class_get_property_from_name(klass, field);
+
+ str = (MonoString*)mono_property_get_value(prop, obj, NULL, NULL);
+
+ return mono_string_to_utf8(str);
+}
+
+gboolean ml_is_api_dll(MonoImage *image)
+{
+ MonoClass *klass;
+ int i, total;
+
+ total = mono_image_get_table_rows (image, MONO_TABLE_TYPEDEF);
+ for (i = 1; i <= total; ++i) {
+ klass = mono_class_get (image, MONO_TOKEN_TYPE_DEF | i);
+ if (strcmp(mono_class_get_name(klass), "Debug") == 0)
+ if (strcmp(mono_class_get_namespace(klass), "Gaim") == 0) {
+ ml_set_api_image(image);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+MonoObject* ml_object_from_gaim_type(GaimType type, gpointer data)
+{
+ return NULL;
+}
+
+MonoObject* ml_object_from_gaim_subtype(GaimSubType type, gpointer data)
+{
+ MonoObject *obj = NULL;
+
+ switch (type) {
+ case GAIM_SUBTYPE_BLIST_BUDDY:
+ obj = gaim_blist_build_buddy_object(data);
+ break;
+ case GAIM_SUBTYPE_STATUS:
+ obj = gaim_status_build_status_object(data);
+ break;
+ default:
+ break;
+ }
+
+ return obj;
+}
+
+MonoObject* ml_create_api_object(char *class_name)
+{
+ MonoObject *obj = NULL;
+ MonoClass *klass = NULL;
+
+ klass = mono_class_from_name(ml_get_api_image(), "Gaim", class_name);
+ if (!klass) {
+ gaim_debug(GAIM_DEBUG_FATAL, "mono", "couldn't find the '%s' class\n", class_name);
+ return NULL;
+ }
+
+ obj = mono_object_new(ml_get_domain(), klass);
+ if (!obj) {
+ gaim_debug(GAIM_DEBUG_FATAL, "mono", "couldn't create the object from class '%s'\n", class_name);
+ return NULL;
+ }
+
+ mono_runtime_object_init(obj);
+
+ return obj;
+}
+
+static MonoDomain *_domain = NULL;
+
+MonoDomain* ml_get_domain(void)
+{
+ return _domain;
+}
+
+void ml_set_domain(MonoDomain *d)
+{
+ _domain = d;
+}
+
+static MonoImage *_api_image = NULL;
+
+void ml_set_api_image(MonoImage *image)
+{
+ _api_image = image;
+}
+
+MonoImage* ml_get_api_image()
+{
+ return _api_image;
+}
+
+void ml_init_internal_calls(void)
+{
+ mono_add_internal_call("Gaim.Debug::_debug", gaim_debug_glue);
+ mono_add_internal_call("Gaim.Signal::_connect", gaim_signal_connect_glue);
+ mono_add_internal_call("Gaim.BuddyList::_get_handle", gaim_blist_get_handle_glue);
+}
+
+static GHashTable *plugins_hash = NULL;
+
+void ml_add_plugin(GaimMonoPlugin *plugin)
+{
+ if (!plugins_hash)
+ plugins_hash = g_hash_table_new(NULL, NULL);
+
+ g_hash_table_insert(plugins_hash, plugin->klass, plugin);
+}
+
+gboolean ml_remove_plugin(GaimMonoPlugin *plugin)
+{
+ return g_hash_table_remove(plugins_hash, plugin->klass);
+}
+
+gpointer ml_find_plugin(GaimMonoPlugin *plugin)
+{
+ return g_hash_table_lookup(plugins_hash, plugin->klass);
+}
+
+gpointer ml_find_plugin_by_class(MonoClass *klass)
+{
+ return g_hash_table_lookup(plugins_hash, klass);
+}
+
+GHashTable* ml_get_plugin_hash()
+{
+ return plugins_hash;
+}
diff --git a/libpurple/plugins/mono/loader/mono-helper.h b/libpurple/plugins/mono/loader/mono-helper.h
new file mode 100644
index 0000000000..d2198d6123
--- /dev/null
+++ b/libpurple/plugins/mono/loader/mono-helper.h
@@ -0,0 +1,73 @@
+#ifndef _GAIM_MONO_LOADER_MONO_HELPER_H_
+#define _GAIM_MONO_LOADER_MONO_HELPER_H_
+
+#include <mono/jit/jit.h>
+#include <mono/metadata/object.h>
+#include <mono/metadata/environment.h>
+#include <mono/metadata/assembly.h>
+#include <mono/metadata/debug-helpers.h>
+#include <mono/metadata/tokentype.h>
+#include "plugin.h"
+#include "value.h"
+#include "debug.h"
+
+typedef struct {
+ GaimPlugin *plugin;
+
+ MonoAssembly *assm;
+ MonoClass *klass;
+ MonoObject *obj;
+
+ MonoMethod *init;
+ MonoMethod *load;
+ MonoMethod *unload;
+ MonoMethod *destroy;
+
+ GList *signal_data;
+} GaimMonoPlugin;
+
+gboolean ml_init(void);
+
+void ml_uninit(void);
+
+MonoObject* ml_invoke(MonoMethod *method, void *obj, void **params);
+
+MonoObject* ml_delegate_invoke(MonoObject *method, void **params);
+
+MonoClass* ml_find_plugin_class(MonoImage *image);
+
+gchar* ml_get_prop_string(MonoObject *obj, char *field);
+
+void ml_set_prop_string(MonoObject *obj, char *field, char *data);
+
+gboolean ml_is_api_dll(MonoImage *image);
+
+MonoDomain* ml_get_domain(void);
+
+void ml_set_domain(MonoDomain *d);
+
+void ml_init_internal_calls(void);
+
+MonoObject* ml_object_from_gaim_type(GaimType type, gpointer data);
+
+MonoObject* ml_object_from_gaim_subtype(GaimSubType type, gpointer data);
+
+MonoObject* ml_create_api_object(char *class_name);
+
+void ml_set_api_image(MonoImage *image);
+
+MonoImage* ml_get_api_image(void);
+
+/* hash table stuff; probably don't need it anymore */
+
+void ml_add_plugin(GaimMonoPlugin *plugin);
+
+gboolean ml_remove_plugin(GaimMonoPlugin *plugin);
+
+gpointer ml_find_plugin(GaimMonoPlugin *plugin);
+
+gpointer ml_find_plugin_by_class(MonoClass *klass);
+
+GHashTable* ml_get_plugin_hash(void);
+
+#endif
diff --git a/libpurple/plugins/mono/loader/mono.c b/libpurple/plugins/mono/loader/mono.c
new file mode 100644
index 0000000000..4539065d3b
--- /dev/null
+++ b/libpurple/plugins/mono/loader/mono.c
@@ -0,0 +1,235 @@
+/*
+ * Mono Plugin Loader
+ *
+ * -- Thanks to the perl plugin loader for all the great tips ;-)
+ *
+ * Eoin Coffey
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "internal.h"
+#include "debug.h"
+#include "plugin.h"
+#include "version.h"
+#include "mono-helper.h"
+
+#define MONO_PLUGIN_ID "core-mono"
+
+/******************************************************************************
+ * Loader Stuff
+ *****************************************************************************/
+/* probes the given plugin to determine if its a plugin */
+static gboolean probe_mono_plugin(GaimPlugin *plugin)
+{
+ MonoAssembly *assm;
+ MonoMethod *m = NULL;
+ MonoMethod *info_method = NULL;
+ MonoObject *plugin_info;
+ gboolean found_load = FALSE, found_unload = FALSE, found_destroy = FALSE, found_info = FALSE;
+ gpointer iter = NULL;
+
+ GaimPluginInfo *info;
+ GaimMonoPlugin *mplug;
+
+ char *file = plugin->path;
+
+ assm = mono_domain_assembly_open(ml_get_domain(), file);
+
+ if (!assm) {
+ return FALSE;
+ }
+
+ gaim_debug(GAIM_DEBUG_INFO, "mono", "Probing plugin\n");
+
+ if (ml_is_api_dll(mono_assembly_get_image(assm))) {
+ gaim_debug(GAIM_DEBUG_INFO, "mono", "Found our GaimAPI.dll\n");
+ return FALSE;
+ }
+
+ info = g_new0(GaimPluginInfo, 1);
+ mplug = g_new0(GaimMonoPlugin, 1);
+
+ mplug->signal_data = NULL;
+
+ mplug->assm = assm;
+
+ mplug->klass = ml_find_plugin_class(mono_assembly_get_image(mplug->assm));
+ if (!mplug->klass) {
+ gaim_debug(GAIM_DEBUG_ERROR, "mono", "no plugin class in \'%s\'\n", file);
+ return FALSE;
+ }
+
+ mplug->obj = mono_object_new(ml_get_domain(), mplug->klass);
+ if (!mplug->obj) {
+ gaim_debug(GAIM_DEBUG_ERROR, "mono", "obj not valid\n");
+ return FALSE;
+ }
+
+ mono_runtime_object_init(mplug->obj);
+
+ while ((m = mono_class_get_methods(mplug->klass, &iter))) {
+ if (strcmp(mono_method_get_name(m), "Load") == 0) {
+ mplug->load = m;
+ found_load = TRUE;
+ } else if (strcmp(mono_method_get_name(m), "Unload") == 0) {
+ mplug->unload = m;
+ found_unload = TRUE;
+ } else if (strcmp(mono_method_get_name(m), "Destroy") == 0) {
+ mplug->destroy = m;
+ found_destroy = TRUE;
+ } else if (strcmp(mono_method_get_name(m), "Info") == 0) {
+ info_method = m;
+ found_info = TRUE;
+ }
+ }
+
+ if (!(found_load && found_unload && found_destroy && found_info)) {
+ gaim_debug(GAIM_DEBUG_ERROR, "mono", "did not find the required methods\n");
+ return FALSE;
+ }
+
+ plugin_info = ml_invoke(info_method, mplug->obj, NULL);
+
+ /* now that the methods are filled out we can populate
+ the info struct with all the needed info */
+
+ info->name = ml_get_prop_string(plugin_info, "Name");
+ info->version = ml_get_prop_string(plugin_info, "Version");
+ info->summary = ml_get_prop_string(plugin_info, "Summary");
+ info->description = ml_get_prop_string(plugin_info, "Description");
+ info->author = ml_get_prop_string(plugin_info, "Author");
+ info->homepage = ml_get_prop_string(plugin_info, "Homepage");
+
+ info->magic = GAIM_PLUGIN_MAGIC;
+ info->major_version = GAIM_MAJOR_VERSION;
+ info->minor_version = GAIM_MINOR_VERSION;
+ info->type = GAIM_PLUGIN_STANDARD;
+
+ /* this plugin depends on us; duh */
+ info->dependencies = g_list_append(info->dependencies, MONO_PLUGIN_ID);
+ mplug->plugin = plugin;
+
+ plugin->info = info;
+ info->extra_info = mplug;
+
+ ml_add_plugin(mplug);
+
+ return gaim_plugin_register(plugin);
+}
+
+/* Loads a Mono Plugin by calling 'load' in the class */
+static gboolean load_mono_plugin(GaimPlugin *plugin)
+{
+ GaimMonoPlugin *mplug;
+
+ gaim_debug(GAIM_DEBUG_INFO, "mono", "Loading plugin\n");
+
+ mplug = (GaimMonoPlugin*)plugin->info->extra_info;
+
+ ml_invoke(mplug->load, mplug->obj, NULL);
+
+ return TRUE;
+}
+
+/* Unloads a Mono Plugin by calling 'unload' in the class */
+static gboolean unload_mono_plugin(GaimPlugin *plugin)
+{
+ GaimMonoPlugin *mplug;
+
+ gaim_debug(GAIM_DEBUG_INFO, "mono", "Unloading plugin\n");
+
+ mplug = (GaimMonoPlugin*)plugin->info->extra_info;
+
+ gaim_signals_disconnect_by_handle((gpointer)mplug->klass);
+ g_list_foreach(mplug->signal_data, (GFunc)g_free, NULL);
+ g_list_free(mplug->signal_data);
+ mplug->signal_data = NULL;
+
+ ml_invoke(mplug->unload, mplug->obj, NULL);
+
+ return TRUE;
+}
+
+static void destroy_mono_plugin(GaimPlugin *plugin)
+{
+ GaimMonoPlugin *mplug;
+
+ gaim_debug(GAIM_DEBUG_INFO, "mono", "Destroying plugin\n");
+
+ mplug = (GaimMonoPlugin*)plugin->info->extra_info;
+
+ ml_invoke(mplug->destroy, mplug->obj, NULL);
+
+ if (plugin->info) {
+ g_free(plugin->info->name);
+ g_free(plugin->info->version);
+ g_free(plugin->info->summary);
+ g_free(plugin->info->description);
+ g_free(plugin->info->author);
+ g_free(plugin->info->homepage);
+ }
+
+ if (mplug) {
+ if (mplug->assm) {
+ mono_assembly_close(mplug->assm);
+ }
+
+ g_free(mplug);
+ mplug = NULL;
+ }
+}
+
+/******************************************************************************
+ * Plugin Stuff
+ *****************************************************************************/
+static void plugin_destroy(GaimPlugin *plugin)
+{
+ ml_uninit();
+}
+
+static GaimPluginLoaderInfo loader_info =
+{
+ NULL,
+ probe_mono_plugin,
+ load_mono_plugin,
+ unload_mono_plugin,
+ destroy_mono_plugin
+};
+
+static GaimPluginInfo info =
+{
+ GAIM_PLUGIN_MAGIC,
+ GAIM_MAJOR_VERSION,
+ GAIM_MINOR_VERSION,
+ GAIM_PLUGIN_LOADER,
+ NULL,
+ 0,
+ NULL,
+ GAIM_PRIORITY_DEFAULT,
+ MONO_PLUGIN_ID,
+ N_("Mono Plugin Loader"),
+ VERSION,
+ N_("Loads .NET plugins with Mono."),
+ N_("Loads .NET plugins with Mono."),
+ "Eoin Coffey <ecoffey@simla.colostate.edu>",
+ GAIM_WEBSITE,
+ NULL,
+ NULL,
+ plugin_destroy,
+ NULL,
+ &loader_info,
+ NULL,
+ NULL
+};
+
+static void init_plugin(GaimPlugin *plugin)
+{
+ ml_init();
+
+ loader_info.exts = g_list_append(loader_info.exts, "dll");
+}
+
+GAIM_INIT_PLUGIN(mono, init_plugin, info)
diff --git a/libpurple/plugins/mono/loader/signal-glue.c b/libpurple/plugins/mono/loader/signal-glue.c
new file mode 100644
index 0000000000..49d13822a1
--- /dev/null
+++ b/libpurple/plugins/mono/loader/signal-glue.c
@@ -0,0 +1,139 @@
+#include "mono-glue.h"
+#include "mono-helper.h"
+#include "debug.h"
+#include "blist.h"
+#include "signals.h"
+#include "value.h"
+
+typedef struct {
+ MonoObject *func;
+ char *signal;
+ GaimValue **values;
+ GaimValue *ret_value;
+ int num_vals;
+} SignalData;
+
+static GaimCallback get_callback(SignalData *sig_data);
+
+static gpointer dispatch_callback(SignalData *sig_data, int num_vals, ...)
+{
+ MonoArray *array;
+ MonoObject *obj;
+ int i;
+ gpointer meth_args[1];
+ gpointer gaim_obj;
+
+ va_list args;
+
+ va_start(args, num_vals);
+
+ array = mono_array_new(ml_get_domain(), mono_get_object_class(), num_vals);
+
+ for (i = 0; i < num_vals; i++) {
+ if (gaim_value_get_type(sig_data->values[i]) == GAIM_TYPE_SUBTYPE) {
+ gaim_obj = va_arg(args, gpointer);
+ obj = ml_object_from_gaim_subtype(gaim_value_get_subtype(sig_data->values[i]), gaim_obj);
+ mono_array_set(array, MonoObject*, i, obj);
+ } else {
+ gaim_obj = va_arg(args, gpointer);
+ obj = ml_object_from_gaim_type(gaim_value_get_type(sig_data->values[i]), gaim_obj);
+ mono_array_set(array, MonoObject*, i, obj);
+ }
+ }
+
+ va_end(args);
+
+ meth_args[0] = array;
+
+ return ml_delegate_invoke(sig_data->func, meth_args);
+}
+
+static void cb_void__pointer(void *arg1, void *data)
+{
+ dispatch_callback((SignalData*)data, ((SignalData*)data)->num_vals, arg1);
+}
+
+static void cb_void__pointer_pointer_pointer(void *arg1, void *arg2, void *arg3, void *data)
+{
+ dispatch_callback((SignalData*)data, ((SignalData*)data)->num_vals, arg1, arg2, arg3);
+}
+
+
+int gaim_signal_connect_glue(MonoObject* h, MonoObject *plugin, MonoString *signal, MonoObject *func)
+{
+ char *sig;
+ void **instance = NULL;
+ SignalData *sig_data;
+ GaimMonoPlugin *mplug;
+ MonoClass *klass;
+
+ sig = mono_string_to_utf8(signal);
+ gaim_debug(GAIM_DEBUG_INFO, "mono", "connecting signal: %s\n", sig);
+
+ instance = (void*)mono_object_unbox(h);
+
+ sig_data = g_new0(SignalData, 1);
+
+ sig_data->func = func;
+ sig_data->signal = sig;
+
+ gaim_signal_get_values(*instance, sig, &sig_data->ret_value, &sig_data->num_vals, &sig_data->values);
+
+ klass = mono_object_get_class(plugin);
+
+ mplug = ml_find_plugin_by_class(klass);
+
+ mplug->signal_data = g_list_append(mplug->signal_data, (gpointer)sig_data);
+
+ return gaim_signal_connect(*instance, sig, (gpointer)klass, get_callback(sig_data), (gpointer)sig_data);
+}
+
+static int determine_index(GaimType type)
+{
+ switch (type) {
+ case GAIM_TYPE_SUBTYPE:
+ case GAIM_TYPE_STRING:
+ case GAIM_TYPE_OBJECT:
+ case GAIM_TYPE_POINTER:
+ case GAIM_TYPE_BOXED:
+ return 1;
+ break;
+ default:
+ return type;
+ break;
+ }
+}
+
+static gpointer callbacks[]= {
+ NULL,
+ cb_void__pointer,
+ NULL,
+ cb_void__pointer_pointer_pointer
+ };
+
+static int callbacks_array_size = sizeof(callbacks) / sizeof(GaimCallback);
+
+
+static GaimCallback get_callback(SignalData *sig_data)
+{
+ int i, index = 0;
+
+ if (sig_data->ret_value == NULL)
+ index = 0;
+ else
+ index = determine_index(gaim_value_get_type(sig_data->ret_value));
+
+ for (i = 0; i < sig_data->num_vals; i++) {
+ index += determine_index(gaim_value_get_type(sig_data->values[i]));
+ }
+
+ gaim_debug(GAIM_DEBUG_INFO, "mono", "get_callback index = %d\n", index);
+
+ if (index >= callbacks_array_size || callbacks[index] == NULL) {
+ gaim_debug(GAIM_DEBUG_ERROR, "mono", "couldn't find a callback function for signal: %s\n", sig_data->signal);
+ return NULL;
+ }
+
+ gaim_debug(GAIM_DEBUG_MISC, "mono", "using callback at index: %d\n", index);
+ return GAIM_CALLBACK(callbacks[index]);
+}
diff --git a/libpurple/plugins/mono/loader/status-glue.c b/libpurple/plugins/mono/loader/status-glue.c
new file mode 100644
index 0000000000..342f6ecc0b
--- /dev/null
+++ b/libpurple/plugins/mono/loader/status-glue.c
@@ -0,0 +1,16 @@
+#include "status.h"
+#include "mono-helper.h"
+#include "mono-glue.h"
+
+MonoObject* gaim_status_build_status_object(void* data)
+{
+ MonoObject *obj = NULL;
+ GaimStatus *status = (GaimStatus*)data;
+
+ obj = ml_create_api_object("Status");
+ g_return_val_if_fail(obj != NULL, NULL);
+
+ ml_set_prop_string(obj, "Id", (char*)gaim_status_get_id(status));
+
+ return obj;
+}
diff --git a/libpurple/plugins/newline.c b/libpurple/plugins/newline.c
new file mode 100644
index 0000000000..a3dbf3ef41
--- /dev/null
+++ b/libpurple/plugins/newline.c
@@ -0,0 +1,91 @@
+/*
+ * Displays messages on a new line, below the nick
+ * Copyright (C) 2004 Stu Tomlinson <stu@nosnilmot.com>
+ *
+ * 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 <string.h>
+
+#include <conversation.h>
+#include <debug.h>
+#include <plugin.h>
+#include <signals.h>
+#include <util.h>
+#include <version.h>
+
+static gboolean
+addnewline_msg_cb(GaimAccount *account, char *sender, char **message,
+ GaimConversation *conv, int *flags, void *data)
+{
+ if (g_ascii_strncasecmp(*message, "/me ", strlen("/me "))) {
+ char *tmp = g_strdup_printf("\n%s", *message);
+ g_free(*message);
+ *message = tmp;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+plugin_load(GaimPlugin *plugin)
+{
+ void *conversation = gaim_conversations_get_handle();
+
+ gaim_signal_connect(conversation, "writing-im-msg",
+ plugin, GAIM_CALLBACK(addnewline_msg_cb), NULL);
+ gaim_signal_connect(conversation, "writing-chat-msg",
+ plugin, GAIM_CALLBACK(addnewline_msg_cb), NULL);
+
+ return TRUE;
+}
+
+static GaimPluginInfo info =
+{
+ GAIM_PLUGIN_MAGIC, /**< magic */
+ GAIM_MAJOR_VERSION, /**< major version */
+ GAIM_MINOR_VERSION, /**< minor version */
+ GAIM_PLUGIN_STANDARD, /**< type */
+ NULL, /**< ui_requirement */
+ 0, /**< flags */
+ NULL, /**< dependencies */
+ GAIM_PRIORITY_DEFAULT, /**< priority */
+
+ "core-plugin_pack-newline", /**< id */
+ N_("New Line"), /**< name */
+ VERSION, /**< version */
+ N_("Prepends a newline to displayed message."), /** summary */
+ N_("Prepends a newline to messages so that the "
+ "rest of the message appears below the "
+ "screen name in the conversation window."), /** description */
+ "Stu Tomlinson <stu@nosnilmot.com>", /**< author */
+ GAIM_WEBSITE, /**< homepage */
+
+ plugin_load, /**< load */
+ NULL, /**< unload */
+ NULL, /**< destroy */
+
+ NULL, /**< ui_info */
+ NULL, /**< extra_info */
+ NULL, /**< prefs_info */
+ NULL /**< actions */
+};
+
+static void
+init_plugin(GaimPlugin *plugin) {
+}
+
+GAIM_INIT_PLUGIN(lastseen, init_plugin, info)
diff --git a/libpurple/plugins/offlinemsg.c b/libpurple/plugins/offlinemsg.c
new file mode 100644
index 0000000000..976f3799e2
--- /dev/null
+++ b/libpurple/plugins/offlinemsg.c
@@ -0,0 +1,239 @@
+/*
+ * Offline Message Emulation - Save messages sent to an offline user as pounce
+ * Copyright (C) 2004
+ *
+ * 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"
+
+#define PLUGIN_ID "core-plugin_pack-offlinemsg"
+#define PLUGIN_NAME N_("Offline Message Emulation")
+#define PLUGIN_STATIC_NAME "offlinemsg"
+#define PLUGIN_SUMMARY N_("Save messages sent to an offline user as pounce.")
+#define PLUGIN_DESCRIPTION N_("Save messages sent to an offline user as pounce.")
+#define PLUGIN_AUTHOR "Sadrul H Chowdhury <sadrul@users.sourceforge.net>"
+
+/* Gaim headers */
+#include <version.h>
+
+#include <blist.h>
+#include <conversation.h>
+#include <core.h>
+#include <debug.h>
+#include <pounce.h>
+#include <request.h>
+
+#define PREF_PREFIX "/plugins/core/" PLUGIN_ID
+#define PREF_ALWAYS PREF_PREFIX "/always"
+
+typedef struct _OfflineMsg OfflineMsg;
+
+typedef enum
+{
+ OFFLINE_MSG_NONE,
+ OFFLINE_MSG_YES,
+ OFFLINE_MSG_NO
+} OfflineMessageSetting;
+
+struct _OfflineMsg
+{
+ GaimAccount *account;
+ GaimConversation *conv;
+ char *who;
+ char *message;
+};
+
+static void
+discard_data(OfflineMsg *offline)
+{
+ g_free(offline->who);
+ g_free(offline->message);
+ g_free(offline);
+}
+
+static void
+cancel_poune(OfflineMsg *offline)
+{
+ gaim_conversation_set_data(offline->conv, "plugin_pack:offlinemsg",
+ GINT_TO_POINTER(OFFLINE_MSG_NO));
+ gaim_conv_im_send_with_flags(GAIM_CONV_IM(offline->conv), offline->message, 0);
+ discard_data(offline);
+}
+
+static void
+record_pounce(OfflineMsg *offline)
+{
+ GaimPounce *pounce;
+ GaimPounceEvent event;
+ GaimPounceOption option;
+ GaimConversation *conv;
+
+ event = GAIM_POUNCE_SIGNON;
+ option = GAIM_POUNCE_OPTION_NONE;
+
+ pounce = gaim_pounce_new(gaim_core_get_ui(), offline->account, offline->who,
+ event, option);
+
+ gaim_pounce_action_set_enabled(pounce, "send-message", TRUE);
+ gaim_pounce_action_set_attribute(pounce, "send-message", "message", offline->message);
+
+ conv = offline->conv;
+ if (!gaim_conversation_get_data(conv, "plugin_pack:offlinemsg"))
+ gaim_conversation_write(conv, NULL, _("The rest of the messages will be saved "
+ "as pounce. You can edit/delete the pounce from the `Buddy "
+ "Pounce' dialog."),
+ GAIM_MESSAGE_SYSTEM, time(NULL));
+ gaim_conversation_set_data(conv, "plugin_pack:offlinemsg",
+ GINT_TO_POINTER(OFFLINE_MSG_YES));
+
+ gaim_conv_im_write(GAIM_CONV_IM(conv), offline->who, offline->message,
+ GAIM_MESSAGE_SEND, time(NULL));
+
+ discard_data(offline);
+}
+
+static void
+sending_msg_cb(GaimAccount *account, const char *who, char **message, gpointer handle)
+{
+ GaimBuddy *buddy;
+ OfflineMsg *offline;
+ GaimConversation *conv;
+ OfflineMessageSetting setting;
+
+ buddy = gaim_find_buddy(account, who);
+ if (!buddy)
+ return;
+
+ if (gaim_presence_is_online(gaim_buddy_get_presence(buddy)))
+ return;
+
+ if (gaim_account_supports_offline_message(account, buddy))
+ {
+ gaim_debug_info("offlinemsg", "Account \"%s\" supports offline message.",
+ gaim_account_get_username(account));
+ return;
+ }
+
+ conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM,
+ who, account);
+
+ if (!conv)
+ return;
+
+ setting = GPOINTER_TO_INT(gaim_conversation_get_data(conv, "plugin_pack:offlinemsg"));
+ if (setting == OFFLINE_MSG_NO)
+ return;
+
+ offline = g_new0(OfflineMsg, 1);
+ offline->conv = conv;
+ offline->account = account;
+ offline->who = g_strdup(who);
+ offline->message = *message;
+ *message = NULL;
+
+ if (gaim_prefs_get_bool(PREF_ALWAYS) || setting == OFFLINE_MSG_YES)
+ record_pounce(offline);
+ else if (setting == OFFLINE_MSG_NONE)
+ {
+ char *ask;
+ ask = g_strdup_printf(_("\"%s\" is currently offline. Do you want to save the "
+ "rest of the messages in a pounce and automatically send them "
+ "when \"%s\" logs back in?"), who, who);
+
+ gaim_request_action(handle, _("Offline Message"), ask,
+ _("You can edit/delete the pounce from the `Buddy Pounces' dialog"),
+ 1, offline, 2,
+ _("Yes"), record_pounce,
+ _("No"), cancel_poune);
+ g_free(ask);
+ }
+}
+
+static gboolean
+plugin_load(GaimPlugin *plugin)
+{
+ gaim_signal_connect(gaim_conversations_get_handle(), "sending-im-msg",
+ plugin, GAIM_CALLBACK(sending_msg_cb), plugin);
+ return TRUE;
+}
+
+static gboolean
+plugin_unload(GaimPlugin *plugin)
+{
+ return TRUE;
+}
+
+static GaimPluginPrefFrame *
+get_plugin_pref_frame(GaimPlugin *plugin)
+{
+ GaimPluginPrefFrame *frame;
+ GaimPluginPref *pref;
+
+ frame = gaim_plugin_pref_frame_new();
+
+ pref = gaim_plugin_pref_new_with_label(_("Save offline messages in pounce"));
+ gaim_plugin_pref_frame_add(frame, pref);
+
+ pref = gaim_plugin_pref_new_with_name_and_label(PREF_ALWAYS,
+ _("Do not ask. Always save in pounce."));
+ gaim_plugin_pref_frame_add(frame, pref);
+
+ return frame;
+}
+
+static GaimPluginUiInfo prefs_info = {
+ get_plugin_pref_frame,
+ 0,
+ NULL
+};
+
+static GaimPluginInfo info =
+{
+ GAIM_PLUGIN_MAGIC, /* Magic */
+ GAIM_MAJOR_VERSION, /* Gaim Major Version */
+ GAIM_MINOR_VERSION, /* Gaim Minor Version */
+ GAIM_PLUGIN_STANDARD, /* plugin type */
+ NULL, /* ui requirement */
+ 0, /* flags */
+ NULL, /* dependencies */
+ GAIM_PRIORITY_DEFAULT, /* priority */
+
+ PLUGIN_ID, /* plugin id */
+ PLUGIN_NAME, /* name */
+ VERSION, /* version */
+ PLUGIN_SUMMARY, /* summary */
+ PLUGIN_DESCRIPTION, /* description */
+ PLUGIN_AUTHOR, /* author */
+ GAIM_WEBSITE, /* website */
+
+ plugin_load, /* load */
+ plugin_unload, /* unload */
+ NULL, /* destroy */
+
+ NULL, /* ui_info */
+ NULL, /* extra_info */
+ &prefs_info, /* prefs_info */
+ NULL /* actions */
+};
+
+static void
+init_plugin(GaimPlugin *plugin)
+{
+ gaim_prefs_add_none(PREF_PREFIX);
+ gaim_prefs_add_bool(PREF_ALWAYS, FALSE);
+}
+
+GAIM_INIT_PLUGIN(PLUGIN_STATIC_NAME, init_plugin, info)
diff --git a/libpurple/plugins/perl/Makefile.am b/libpurple/plugins/perl/Makefile.am
new file mode 100644
index 0000000000..e2d3e3e399
--- /dev/null
+++ b/libpurple/plugins/perl/Makefile.am
@@ -0,0 +1,168 @@
+plugindir = $(libdir)/gaim
+hackdir = $(plugindir)/private
+
+perl_dirs = common
+
+plugin_LTLIBRARIES = perl.la
+hack_LTLIBRARIES = libgaimperl.la
+
+perl_la_LDFLAGS = -module -avoid-version
+perl_la_LIBADD = $(GLIB_LIBS) $(PERL_LIBS) libgaimperl.la
+perl_la_SOURCES = \
+ perl.c \
+ perl-common.c \
+ perl-common.h \
+ perl-handlers.c \
+ perl-handlers.h
+
+perl_la_DEPENDENCIES = \
+ .libs/libperl_orig.a \
+ .libs/DynaLoader.a \
+ libgaimperl.la
+
+libgaimperl_la_LDFLAGS = -module -avoid-version
+libgaimperl_la_LIBADD = $(GLIB_LIBS)
+libgaimperl_la_SOURCES = libgaimperl.c
+
+.libs/libperl_orig.a:
+ @mkdir -p .libs
+ @rm -f .libs/libperl_orig.a
+ @if [ x$(LIBPERL_A) = x ]; then \
+ touch .libs/libperl_orig.a; \
+ else \
+ $(LN_S) $(LIBPERL_A) .libs/libperl_orig.a; \
+ fi
+
+.libs/DynaLoader.a:
+ @mkdir -p .libs
+ @rm -f .libs/DynaLoader.a
+ @if [ x$(DYNALOADER_A) = x ]; then \
+ touch .libs/DynaLoader.a; \
+ else \
+ $(LN_S) $(DYNALOADER_A) .libs/DynaLoader.a; \
+ fi
+
+
+common_sources = \
+ common/Account.xs \
+ common/AccountOpts.xs \
+ common/BuddyIcon.xs \
+ common/BuddyList.xs \
+ common/Cipher.xs \
+ common/Cmds.xs \
+ common/Core.xs \
+ common/Connection.xs \
+ common/Conversation.xs \
+ common/Debug.xs \
+ common/FT.xs \
+ common/Gaim.pm \
+ common/Gaim.xs \
+ common/ImgStore.xs \
+ common/Log.xs \
+ common/Makefile.PL.in \
+ common/Network.xs \
+ common/Notify.xs \
+ common/Plugin.xs \
+ common/PluginPref.xs \
+ common/Pounce.xs \
+ common/Prefs.xs \
+ common/Privacy.xs \
+ common/Proxy.xs \
+ common/Prpl.xs \
+ common/Request.xs \
+ common/Roomlist.xs \
+ common/SSLConn.xs \
+ common/SavedStatuses.xs \
+ common/Server.xs \
+ common/Signal.xs \
+ common/Sound.xs \
+ common/Status.xs \
+ common/Stringref.xs \
+ common/Util.xs \
+ common/XMLNode.xs \
+ common/fallback/const-c.inc \
+ common/fallback/const-xs.inc \
+ common/module.h \
+ common/typemap
+
+
+EXTRA_DIST = \
+ Makefile.mingw \
+ common/Makefile.mingw \
+ $(common_sources) \
+ libgaimperl.c
+
+common/Makefile: common/Makefile.PL
+ @if test "x${top_srcdir}" != "x${top_builddir}"; then \
+ for f in ${common_sources}; do \
+ ${LN_S} -f ../${srcdir}/$$f $$f; \
+ done; \
+ fi
+ @cd common && $(perlpath) Makefile.PL $(PERL_MM_PARAMS)
+
+common/Makefile.PL: common/Makefile.PL.in $(top_builddir)/config.status
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)
+
+all-local: common/Makefile
+ @for dir in $(perl_dirs); do \
+ cd $$dir && \
+ if [ ! -f Makefile ]; then \
+ $(perlpath) Makefile.PL $(PERL_MM_PARAMS); \
+ fi && \
+ ($(MAKE) CC="$(CC)" CCFLAGS="$(PERL_CFLAGS) $(CFLAGS)" $(PERL_EXTRA_OPTS) || \
+ $(MAKE) CC="$(CC)" CCFLAGS="$(PERL_CFLAGS) $(CFLAGS)" $(PERL_EXTRA_OPTS)) && \
+ cd ..; \
+ done
+
+install-exec-local:
+ @for dir in $(perl_dirs); do \
+ cd $$dir; \
+ $(MAKE) install; \
+ cd ..; \
+ done
+
+# Evil Hack (TM)
+# ... which doesn't work with DESTDIR installs. FIXME?
+uninstall-local:
+ @for dir in $(perl_dirs); do \
+ cd $$dir && \
+ `$(MAKE) uninstall | grep unlink | sed -e 's#/usr#${prefix}#' -e 's#unlink#rm -f#'` && \
+ cd ..; \
+ done
+
+clean-generic:
+ @for dir in $(perl_dirs); do \
+ cd $$dir; \
+ $(MAKE) clean; \
+ cd ..; \
+ done
+ rm -f *.so
+
+distclean-generic:
+ @for dir in $(perl_dirs); do \
+ cd $$dir; \
+ $(MAKE) realclean; \
+ rm -f Makefile.PL; \
+ rm -f Makefile.old; \
+ rm -f Makefile; \
+ cd ..; \
+ done
+
+ @rm -f Makefile
+ @rm -f common/const-c.inc common/const-xs.inc
+
+ @if test "x${top_srcdir}" != "x${top_builddir}"; then \
+ for f in ${common_sources}; do \
+ ${LN_S} -f ../${srcdir}/$$f $$f; \
+ done; \
+ fi
+
+
+AM_CPPFLAGS = \
+ -DVERSION=\"$(VERSION)\" \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/libpurple \
+ $(DEBUG_CFLAGS) \
+ $(GLIB_CFLAGS) \
+ $(PLUGIN_CFLAGS) \
+ $(PERL_CFLAGS)
diff --git a/libpurple/plugins/perl/Makefile.mingw b/libpurple/plugins/perl/Makefile.mingw
new file mode 100755
index 0000000000..91d9f330dc
--- /dev/null
+++ b/libpurple/plugins/perl/Makefile.mingw
@@ -0,0 +1,82 @@
+#
+# Makefile.mingw
+#
+# Description: Makefile for perl plugin loader plugin.
+#
+
+GAIM_TOP := ../../..
+include $(GAIM_TOP)/libgaim/win32/global.mak
+
+TARGET = perl
+
+# Perl headers with /* /* */ type comments.. Turn off warnings.
+CFLAGS += -Wno-comment
+
+##
+## INCLUDE PATHS
+##
+INCLUDE_PATHS += -I. \
+ -I$(GAIM_TOP) \
+ -I$(GAIM_LIB_TOP) \
+ -I$(GAIM_LIB_TOP)/win32 \
+ -I$(GTK_TOP)/include \
+ -I$(GTK_TOP)/include/glib-2.0 \
+ -I$(GTK_TOP)/lib/glib-2.0/include \
+ -I$(PERL_LIB_TOP)/CORE
+
+LIB_PATHS = -L$(GTK_TOP)/lib \
+ -L$(GAIM_LIB_TOP) \
+ -L$(PERL_LIB_TOP)
+
+##
+## SOURCES, OBJECTS
+##
+C_SRC = perl.c \
+ perl-common.c \
+ perl-handlers.c
+
+OBJECTS = $(C_SRC:%.c=%.o)
+
+##
+## LIBRARIES
+##
+LIBS = \
+ -lglib-2.0 \
+ -lgmodule-2.0 \
+ -lgobject-2.0 \
+ -lws2_32 \
+ -lintl \
+ -lgaim \
+ -lperl58
+
+include $(GAIM_COMMON_RULES)
+
+##
+## TARGET DEFINITIONS
+##
+.PHONY: all install clean
+
+all: $(TARGET).dll
+ $(MAKE) -C ./common -f $(GAIM_WIN32_MAKEFILE)
+
+install: all $(GAIM_INSTALL_PLUGINS_DIR)
+ cp $(TARGET).dll $(GAIM_INSTALL_PLUGINS_DIR)
+ $(MAKE) -C ./common -f $(GAIM_WIN32_MAKEFILE) install
+
+$(OBJECTS): $(GAIM_CONFIG_H)
+
+##
+## BUILD DLL
+##
+$(TARGET).dll $(TARGET).dll.a: $(GAIM_LIBGAIM_DLL).a $(OBJECTS)
+ $(CC) -shared $(OBJECTS) $(LIB_PATHS) $(LIBS) $(DLL_LD_FLAGS) -Wl,--export-all-symbols -Wl,--out-implib,$(TARGET).dll.a -o $(TARGET).dll
+
+##
+## CLEAN RULES
+##
+clean:
+ rm -rf $(OBJECTS)
+ rm -rf $(TARGET).dll $(TARGET).dll.a
+ $(MAKE) -C ./common -f $(GAIM_WIN32_MAKEFILE) clean
+
+include $(GAIM_COMMON_TARGETS)
diff --git a/libpurple/plugins/perl/common/Account.xs b/libpurple/plugins/perl/common/Account.xs
new file mode 100644
index 0000000000..c419fc5a82
--- /dev/null
+++ b/libpurple/plugins/perl/common/Account.xs
@@ -0,0 +1,326 @@
+#include "module.h"
+
+MODULE = Gaim::Account PACKAGE = Gaim::Account PREFIX = gaim_account_
+PROTOTYPES: ENABLE
+
+Gaim::Presence
+gaim_account_get_presence(account)
+ Gaim::Account account
+
+Gaim::Account
+gaim_account_new(class, username, protocol_id)
+ const char * username
+ const char * protocol_id
+ C_ARGS:
+ username, protocol_id
+
+void
+gaim_account_destroy(account)
+ Gaim::Account account
+
+void
+gaim_account_connect(account)
+ Gaim::Account account
+
+void
+gaim_account_register(account)
+ Gaim::Account account
+
+void
+gaim_account_disconnect(account)
+ Gaim::Account account
+
+void
+gaim_account_request_change_password(account)
+ Gaim::Account account
+
+void
+gaim_account_request_change_user_info(account)
+ Gaim::Account account
+
+void
+gaim_account_set_username(account, username)
+ Gaim::Account account
+ const char * username
+
+void
+gaim_account_set_password(account, password)
+ Gaim::Account account
+ const char * password
+
+void
+gaim_account_set_alias(account, alias)
+ Gaim::Account account
+ const char * alias
+
+void
+gaim_account_set_user_info(account, user_info)
+ Gaim::Account account
+ const char *user_info
+
+void
+gaim_account_set_buddy_icon(account, icon)
+ Gaim::Account account
+ const char *icon
+
+void
+gaim_account_set_connection(account, gc)
+ Gaim::Account account
+ Gaim::Connection gc
+
+void
+gaim_account_set_remember_password(account, value)
+ Gaim::Account account
+ gboolean value
+
+void
+gaim_account_set_check_mail(account, value)
+ Gaim::Account account
+ gboolean value
+
+void gaim_account_set_enabled(account, ui, value)
+ Gaim::Account account
+ const char *ui
+ gboolean value
+
+void
+gaim_account_set_proxy_info(account, info)
+ Gaim::Account account
+ Gaim::ProxyInfo info
+
+void
+gaim_account_set_status(account, status_id, active)
+ Gaim::Account account
+ const char *status_id
+ gboolean active
+CODE:
+ gaim_account_set_status(account, status_id, active, NULL);
+
+void
+gaim_account_set_status_types(account, status_types)
+ Gaim::Account account
+ SV * status_types
+PREINIT:
+ GList *t_GL;
+ int i, t_len;
+PPCODE:
+ t_GL = NULL;
+ t_len = av_len((AV *)SvRV(status_types));
+
+ for (i = 0; i < t_len; i++) {
+ STRLEN t_sl;
+ t_GL = g_list_append(t_GL, SvPV(*av_fetch((AV *)SvRV(status_types), i, 0), t_sl));
+ }
+ gaim_account_set_status_types(account, t_GL);
+
+void
+gaim_account_clear_settings(account)
+ Gaim::Account account
+
+void
+gaim_account_set_int(account, name, value)
+ Gaim::Account account
+ const char *name
+ int value
+
+gboolean
+gaim_account_is_connected(account)
+ Gaim::Account account
+
+const char *
+gaim_account_get_username(account)
+ Gaim::Account account
+
+const char *
+gaim_account_get_password(account)
+ Gaim::Account account
+
+const char *
+gaim_account_get_alias(account)
+ Gaim::Account account
+
+const char *
+gaim_account_get_user_info(account)
+ Gaim::Account account
+
+const char *
+gaim_account_get_buddy_icon(account)
+ Gaim::Account account
+
+const char *
+gaim_account_get_protocol_id(account)
+ Gaim::Account account
+
+const char *
+gaim_account_get_protocol_name(account)
+ Gaim::Account account
+
+Gaim::Connection
+gaim_account_get_connection(account)
+ Gaim::Account account
+
+gboolean
+gaim_account_get_remember_password(account)
+ Gaim::Account account
+
+gboolean
+gaim_account_get_check_mail(account)
+ Gaim::Account account
+
+gboolean
+gaim_account_get_enabled(account, ui)
+ Gaim::Account account
+ const char *ui
+
+Gaim::ProxyInfo
+gaim_account_get_proxy_info(account)
+ Gaim::Account account
+
+Gaim::Status
+gaim_account_get_active_status(account)
+ Gaim::Account account
+
+void
+gaim_account_get_status_types(account)
+ Gaim::Account account
+PREINIT:
+ const GList *l;
+PPCODE:
+ for (l = gaim_account_get_status_types(account); l != NULL; l = l->next) {
+ XPUSHs(sv_2mortal(gaim_perl_bless_object(l->data, "Gaim::StatusType")));
+ }
+
+Gaim::Log
+gaim_account_get_log(account, create)
+ Gaim::Account account
+ gboolean create
+
+void
+gaim_account_destroy_log(account)
+ Gaim::Account account
+
+void
+gaim_account_add_buddies(account, list)
+ Gaim::Account account
+ SV * list
+PREINIT:
+ GList *t_GL;
+ int i, t_len;
+PPCODE:
+ t_GL = NULL;
+ t_len = av_len((AV *)SvRV(list));
+
+ for (i = 0; i < t_len; i++) {
+ STRLEN t_sl;
+ t_GL = g_list_append(t_GL, SvPV(*av_fetch((AV *)SvRV(list), i, 0), t_sl));
+ }
+ gaim_account_add_buddies(account, t_GL);
+
+void
+gaim_account_add_buddy(account, buddy)
+ Gaim::Account account
+ Gaim::BuddyList::Buddy buddy
+
+void
+gaim_account_change_password(account, a, b)
+ Gaim::Account account
+ const char * a
+ const char * b
+
+void
+gaim_account_remove_buddies(account, A, B)
+ Gaim::Account account
+ SV * A
+ SV * B
+PREINIT:
+ GList *t_GL1, *t_GL2;
+ int i, t_len;
+PPCODE:
+ t_GL1 = NULL;
+ t_len = av_len((AV *)SvRV(A));
+
+ for (i = 0; i < t_len; i++) {
+ STRLEN t_sl;
+ t_GL1 = g_list_append(t_GL1, SvPV(*av_fetch((AV *)SvRV(A), i, 0), t_sl));
+ }
+
+ t_GL2 = NULL;
+ t_len = av_len((AV *)SvRV(B));
+
+ for (i = 0; i < t_len; i++) {
+ STRLEN t_sl;
+ t_GL2 = g_list_append(t_GL2, SvPV(*av_fetch((AV *)SvRV(B), i, 0), t_sl));
+ }
+ gaim_account_remove_buddies(account, t_GL1, t_GL2);
+
+void
+gaim_account_remove_buddy(account, buddy, group)
+ Gaim::Account account
+ Gaim::BuddyList::Buddy buddy
+ Gaim::BuddyList::Group group
+
+void
+gaim_account_remove_group(account, group)
+ Gaim::Account account
+ Gaim::BuddyList::Group group
+
+MODULE = Gaim::Account PACKAGE = Gaim::Accounts PREFIX = gaim_accounts_
+PROTOTYPES: ENABLE
+
+void
+gaim_accounts_add(account)
+ Gaim::Account account
+
+void
+gaim_accounts_remove(account)
+ Gaim::Account account
+
+void
+gaim_accounts_delete(account)
+ Gaim::Account account
+
+void
+gaim_accounts_reorder(account, new_index)
+ Gaim::Account account
+ size_t new_index
+
+void
+gaim_accounts_get_all()
+PREINIT:
+ GList *l;
+PPCODE:
+ for (l = gaim_accounts_get_all(); l != NULL; l = l->next) {
+ XPUSHs(sv_2mortal(gaim_perl_bless_object(l->data, "Gaim::Account")));
+ }
+
+void
+gaim_accounts_get_all_active()
+PREINIT:
+ GList *list, *iter;
+PPCODE:
+ list = gaim_accounts_get_all_active();
+ for (iter = list; iter != NULL; iter = iter->next) {
+ XPUSHs(sv_2mortal(gaim_perl_bless_object(iter->data, "Gaim::Account")));
+ }
+ g_list_free(list);
+
+Gaim::Account
+gaim_accounts_find(name, protocol)
+ const char * name
+ const char * protocol
+
+void
+gaim_accounts_set_ui_ops(ops)
+ Gaim::Account::UiOps ops
+
+Gaim::Account::UiOps
+gaim_accounts_get_ui_ops()
+
+Gaim::Handle
+gaim_accounts_get_handle()
+
+void
+gaim_accounts_init()
+
+void
+gaim_accounts_uninit()
diff --git a/libpurple/plugins/perl/common/AccountOpts.xs b/libpurple/plugins/perl/common/AccountOpts.xs
new file mode 100644
index 0000000000..a9a4eee9e0
--- /dev/null
+++ b/libpurple/plugins/perl/common/AccountOpts.xs
@@ -0,0 +1,168 @@
+#include "module.h"
+
+MODULE = Gaim::Account::Option PACKAGE = Gaim::Account::Option PREFIX = gaim_account_option_
+PROTOTYPES: ENABLE
+
+void
+gaim_account_option_destroy(option)
+ Gaim::Account::Option option
+
+const char *
+gaim_account_option_get_default_string(option)
+ Gaim::Account::Option option
+
+void
+gaim_account_option_add_list_item(option, key, value)
+ Gaim::Account::Option option
+ const char * key
+ const char * value
+
+void
+gaim_account_option_set_default_string(option, value);
+ Gaim::Account::Option option
+ const char * value
+
+void
+gaim_account_option_set_default_int(option, value);
+ Gaim::Account::Option option
+ int value
+
+void
+gaim_account_option_set_default_bool(option, value);
+ Gaim::Account::Option option
+ gboolean value
+
+Gaim::Account::Option
+gaim_account_option_list_new(class, text, pref_name, values)
+ const char * text
+ const char * pref_name
+ SV * values
+PREINIT:
+ GList *t_GL;
+ int i, t_len;
+CODE:
+ t_GL = NULL;
+ t_len = av_len((AV *)SvRV(values));
+
+ for (i = 0; i < t_len; i++) {
+ STRLEN t_sl;
+ t_GL = g_list_append(t_GL, SvPV(*av_fetch((AV *)SvRV(values), i, 0), t_sl));
+ }
+ RETVAL = gaim_account_option_list_new(text, pref_name, t_GL);
+OUTPUT:
+ RETVAL
+
+Gaim::Account::Option
+gaim_account_option_string_new(class, text, pref_name, default_value)
+ const char * text
+ const char * pref_name
+ const char * default_value
+ C_ARGS:
+ text, pref_name, default_value
+
+Gaim::Account::Option
+gaim_account_option_int_new(class, text, pref_name, default_value)
+ const char * text
+ const char * pref_name
+ gboolean default_value
+ C_ARGS:
+ text, pref_name, default_value
+
+Gaim::Account::Option
+gaim_account_option_bool_new(class, text, pref_name, default_value)
+ const char * text
+ const char * pref_name
+ gboolean default_value
+ C_ARGS:
+ text, pref_name, default_value
+
+Gaim::Account::Option
+gaim_account_option_new(class, type, text, pref_name)
+ Gaim::PrefType type
+ const char * text
+ const char * pref_name
+ C_ARGS:
+ type, text, pref_name
+
+void
+gaim_account_option_get_list(option)
+ Gaim::Account::Option option
+PREINIT:
+ const GList *l;
+PPCODE:
+ for (l = gaim_account_option_get_list(option); l != NULL; l = l->next) {
+ /* XXX These are actually GaimKeyValuePairs but we don't have a
+ * type for that and even if we did I don't think there's
+ * anything perl could do with them, so I'm just going to
+ * leave this as a Gaim::ListEntry for now. */
+ XPUSHs(sv_2mortal(gaim_perl_bless_object(l->data, "Gaim::ListEntry")));
+ }
+
+Gaim::PrefType
+gaim_account_option_get_type(option)
+ Gaim::Account::Option option
+
+gboolean
+gaim_account_option_get_masked(option)
+ Gaim::Account::Option option
+
+int
+gaim_account_option_get_default_int(option)
+ Gaim::Account::Option option;
+
+gboolean
+gaim_account_option_get_default_bool(option)
+ Gaim::Account::Option option;
+
+const char *
+gaim_account_option_get_setting(option)
+ Gaim::Account::Option option
+
+const char *
+gaim_account_option_get_text(option)
+ Gaim::Account::Option option
+
+void
+gaim_account_option_set_list(option, values)
+ Gaim::Account::Option option
+ SV * values
+PREINIT:
+ GList *t_GL;
+ int i, t_len;
+PPCODE:
+ t_GL = NULL;
+ t_len = av_len((AV *)SvRV(values));
+
+ for (i = 0; i < t_len; i++) {
+ STRLEN t_sl;
+ t_GL = g_list_append(t_GL, SvPV(*av_fetch((AV *)SvRV(values), i, 0), t_sl));
+ }
+ gaim_account_option_set_list(option, t_GL);
+
+void
+gaim_account_option_set_masked(option, masked)
+ Gaim::Account::Option option
+ gboolean masked
+
+MODULE = Gaim::Account::Option PACKAGE = Gaim::Account::UserSplit PREFIX = gaim_account_user_split_
+PROTOTYPES: ENABLE
+
+Gaim::Account::UserSplit
+gaim_account_user_split_new(class, text, default_value, sep)
+ const char * text
+ const char * default_value
+ char sep
+ C_ARGS:
+ text, default_value, sep
+
+char
+gaim_account_user_split_get_separator(split)
+ Gaim::Account::UserSplit split
+
+const char *
+gaim_account_user_split_get_text(split)
+ Gaim::Account::UserSplit split
+
+void
+gaim_account_user_split_destroy(split)
+ Gaim::Account::UserSplit split
diff --git a/libpurple/plugins/perl/common/BuddyIcon.xs b/libpurple/plugins/perl/common/BuddyIcon.xs
new file mode 100644
index 0000000000..e109631b73
--- /dev/null
+++ b/libpurple/plugins/perl/common/BuddyIcon.xs
@@ -0,0 +1,90 @@
+#include "module.h"
+
+MODULE = Gaim::Buddy::Icon PACKAGE = Gaim::Buddy::Icon PREFIX = gaim_buddy_icon_
+PROTOTYPES: ENABLE
+
+void
+gaim_buddy_icon_destroy(icon)
+ Gaim::Buddy::Icon icon
+
+Gaim::Buddy::Icon
+gaim_buddy_icon_ref(icon)
+ Gaim::Buddy::Icon icon
+
+Gaim::Buddy::Icon
+gaim_buddy_icon_unref(icon)
+ Gaim::Buddy::Icon icon
+
+void
+gaim_buddy_icon_update(icon)
+ Gaim::Buddy::Icon icon
+
+void
+gaim_buddy_icon_cache(icon, buddy)
+ Gaim::Buddy::Icon icon
+ Gaim::BuddyList::Buddy buddy
+
+void
+gaim_buddy_icon_set_account(icon, account)
+ Gaim::Buddy::Icon icon
+ Gaim::Account account
+
+void
+gaim_buddy_icon_set_username(icon, username)
+ Gaim::Buddy::Icon icon
+ const char * username
+
+void
+gaim_buddy_icon_set_data(icon, data, len)
+ Gaim::Buddy::Icon icon
+ void * data
+ size_t len
+
+Gaim::Account
+gaim_buddy_icon_get_account(icon)
+ Gaim::Buddy::Icon icon
+
+const char *
+gaim_buddy_icon_get_username(icon)
+ Gaim::Buddy::Icon icon
+
+const void *
+gaim_buddy_icon_get_data(icon, len)
+ Gaim::Buddy::Icon icon
+ size_t &len
+
+const char *
+gaim_buddy_icon_get_type(icon)
+ Gaim::Buddy::Icon icon
+
+void
+gaim_buddy_icon_get_scale_size(spec, width, height)
+ Gaim::Buddy::Icon::Spec spec
+ int *width
+ int *height
+
+MODULE = Gaim::Buddy::Icon PACKAGE = Gaim::Buddy::Icons PREFIX = gaim_buddy_icons_
+PROTOTYPES: ENABLE
+
+void
+gaim_buddy_icons_set_caching(caching)
+ gboolean caching
+
+gboolean
+gaim_buddy_icons_is_caching()
+
+void
+gaim_buddy_icons_set_cache_dir(cache_dir)
+ const char *cache_dir
+
+const char *
+gaim_buddy_icons_get_cache_dir();
+
+Gaim::Handle
+gaim_buddy_icons_get_handle();
+
+void
+gaim_buddy_icons_init();
+
+void
+gaim_buddy_icons_uninit()
diff --git a/libpurple/plugins/perl/common/BuddyList.xs b/libpurple/plugins/perl/common/BuddyList.xs
new file mode 100644
index 0000000000..c4091743c3
--- /dev/null
+++ b/libpurple/plugins/perl/common/BuddyList.xs
@@ -0,0 +1,383 @@
+#include "module.h"
+#include "../perl-handlers.h"
+
+MODULE = Gaim::BuddyList PACKAGE = Gaim PREFIX = gaim_
+PROTOTYPES: ENABLE
+
+Gaim::BuddyList
+gaim_get_blist()
+
+void
+gaim_set_blist(blist)
+ Gaim::BuddyList blist
+
+MODULE = Gaim::BuddyList PACKAGE = Gaim::Find PREFIX = gaim_find_
+PROTOTYPES: ENABLE
+
+Gaim::BuddyList::Buddy
+gaim_find_buddy(account, name)
+ Gaim::Account account
+ const char * name
+
+void
+gaim_find_buddies(account, name)
+ Gaim::Account account
+ const char * name
+PREINIT:
+ GSList *l;
+PPCODE:
+ for (l = gaim_find_buddies(account, name); l != NULL; l = l->next) {
+ XPUSHs(sv_2mortal(gaim_perl_bless_object(l->data, "Gaim::BuddyList::Buddy")));
+ }
+
+gboolean
+gaim_group_on_account(group, account)
+ Gaim::BuddyList::Group group
+ Gaim::Account account
+
+Gaim::BuddyList::Group
+gaim_find_group(name)
+ const char *name
+
+MODULE = Gaim::BuddyList PACKAGE = Gaim::BuddyList::Contact PREFIX = gaim_contact_
+PROTOTYPES: ENABLE
+
+Gaim::BuddyList::Contact
+gaim_contact_new();
+
+Gaim::BuddyList::Buddy
+gaim_contact_get_priority_buddy(contact)
+ Gaim::BuddyList::Contact contact
+
+void
+gaim_contact_set_alias(contact, alias)
+ Gaim::BuddyList::Contact contact
+ const char * alias
+
+const char *
+gaim_contact_get_alias(contact)
+ Gaim::BuddyList::Contact contact
+
+gboolean
+gaim_contact_on_account(contact, account)
+ Gaim::BuddyList::Contact contact
+ Gaim::Account account
+
+void
+gaim_contact_invalidate_priority_buddy(contact)
+ Gaim::BuddyList::Contact contact
+
+MODULE = Gaim::BuddyList PACKAGE = Gaim::BuddyList::Group PREFIX = gaim_group_
+PROTOTYPES: ENABLE
+
+Gaim::BuddyList::Group
+gaim_group_new(name)
+ const char *name
+
+void
+gaim_group_get_accounts(group)
+ Gaim::BuddyList::Group group
+PREINIT:
+ GSList *l;
+PPCODE:
+ for (l = gaim_group_get_accounts(group); l != NULL; l = l->next) {
+ XPUSHs(sv_2mortal(gaim_perl_bless_object(l->data, "Gaim::Account")));
+ }
+
+gboolean
+gaim_group_on_account(group, account)
+ Gaim::BuddyList::Group group
+ Gaim::Account account
+
+MODULE = Gaim::BuddyList PACKAGE = Gaim::BuddyList PREFIX = gaim_blist_
+PROTOTYPES: ENABLE
+
+void
+gaim_blist_add_contact(contact, group, node)
+ Gaim::BuddyList::Contact contact
+ Gaim::BuddyList::Group group
+ Gaim::BuddyList::Node node
+
+void
+gaim_blist_merge_contact(source, node)
+ Gaim::BuddyList::Contact source
+ Gaim::BuddyList::Node node
+
+void
+gaim_blist_add_group(group, node)
+ Gaim::BuddyList::Group group
+ Gaim::BuddyList::Node node
+
+void
+gaim_blist_add_buddy(buddy, contact, group, node)
+ Gaim::BuddyList::Buddy buddy
+ Gaim::BuddyList::Contact contact
+ Gaim::BuddyList::Group group
+ Gaim::BuddyList::Node node
+
+void
+gaim_blist_remove_buddy(buddy)
+ Gaim::BuddyList::Buddy buddy
+
+void
+gaim_blist_remove_contact(contact)
+ Gaim::BuddyList::Contact contact
+
+void
+gaim_blist_remove_chat(chat)
+ Gaim::BuddyList::Chat chat
+
+void
+gaim_blist_remove_group(group)
+ Gaim::BuddyList::Group group
+
+Gaim::BuddyList::Chat
+gaim_blist_find_chat(account, name)
+ Gaim::Account account
+ const char *name
+
+void
+gaim_blist_add_chat(chat, group, node)
+ Gaim::BuddyList::Chat chat
+ Gaim::BuddyList::Group group
+ Gaim::BuddyList::Node node
+
+Gaim::BuddyList
+gaim_blist_new()
+
+void
+gaim_blist_show()
+
+void
+gaim_blist_destroy();
+
+void
+gaim_blist_set_visible(show)
+ gboolean show
+
+void
+gaim_blist_update_buddy_status(buddy, old_status)
+ Gaim::BuddyList::Buddy buddy
+ Gaim::Status old_status
+
+void
+gaim_blist_update_buddy_icon(buddy)
+ Gaim::BuddyList::Buddy buddy
+
+void
+gaim_blist_rename_buddy(buddy, name)
+ Gaim::BuddyList::Buddy buddy
+ const char * name
+
+void
+gaim_blist_alias_buddy(buddy, alias)
+ Gaim::BuddyList::Buddy buddy
+ const char * alias
+
+void
+gaim_blist_server_alias_buddy(buddy, alias)
+ Gaim::BuddyList::Buddy buddy
+ const char * alias
+
+void
+gaim_blist_alias_chat(chat, alias)
+ Gaim::BuddyList::Chat chat
+ const char * alias
+
+void
+gaim_blist_rename_group(group, name)
+ Gaim::BuddyList::Group group
+ const char * name
+
+void
+gaim_blist_add_account(account)
+ Gaim::Account account
+
+void
+gaim_blist_remove_account(account)
+ Gaim::Account account
+
+int
+gaim_blist_get_group_size(group, offline)
+ Gaim::BuddyList::Group group
+ gboolean offline
+
+int
+gaim_blist_get_group_online_count(group)
+ Gaim::BuddyList::Group group
+
+void
+gaim_blist_load()
+
+void
+gaim_blist_schedule_save()
+
+void
+gaim_blist_request_add_group()
+
+void
+gaim_blist_set_ui_ops(ops)
+ Gaim::BuddyList::UiOps ops
+
+Gaim::BuddyList::UiOps
+gaim_blist_get_ui_ops()
+
+Gaim::Handle
+gaim_blist_get_handle()
+
+void
+gaim_blist_init()
+
+void
+gaim_blist_uninit()
+
+MODULE = Gaim::BuddyList PACKAGE = Gaim::BuddyList::Node PREFIX = gaim_blist_node_
+PROTOTYPES: ENABLE
+
+void
+gaim_blist_node_get_extended_menu(node)
+ Gaim::BuddyList::Node node
+PREINIT:
+ GList *l;
+PPCODE:
+ for (l = gaim_blist_node_get_extended_menu(node); l != NULL; l = l->next) {
+ XPUSHs(sv_2mortal(gaim_perl_bless_object(l->data, "Gaim::Menu::Action")));
+ }
+
+void
+gaim_blist_node_set_bool(node, key, value)
+ Gaim::BuddyList::Node node
+ const char * key
+ gboolean value
+
+gboolean
+gaim_blist_node_get_bool(node, key)
+ Gaim::BuddyList::Node node
+ const char * key
+
+void
+gaim_blist_node_set_int(node, key, value)
+ Gaim::BuddyList::Node node
+ const char * key
+ int value
+
+int
+gaim_blist_node_get_int(node, key)
+ Gaim::BuddyList::Node node
+ const char * key
+
+const char *
+gaim_blist_node_get_string(node, key)
+ Gaim::BuddyList::Node node
+ const char * key
+
+void
+gaim_blist_node_remove_setting(node, key)
+ Gaim::BuddyList::Node node
+ const char * key
+
+void
+gaim_blist_node_set_flags(node, flags)
+ Gaim::BuddyList::Node node
+ Gaim::BuddyList::NodeFlags flags
+
+Gaim::BuddyList::NodeFlags
+gaim_blist_node_get_flags(node)
+ Gaim::BuddyList::Node node
+
+MODULE = Gaim::BuddyList PACKAGE = Gaim::BuddyList::Chat PREFIX = gaim_chat_
+PROTOTYPES: ENABLE
+
+Gaim::BuddyList::Group
+gaim_chat_get_group(chat)
+ Gaim::BuddyList::Chat chat
+
+const char *
+gaim_chat_get_name(chat)
+ Gaim::BuddyList::Chat chat
+
+Gaim::BuddyList::Chat
+gaim_chat_new(account, alias, components)
+ Gaim::Account account
+ const char * alias
+ SV * components
+INIT:
+ HV * t_HV;
+ HE * t_HE;
+ SV * t_SV;
+ GHashTable * t_GHash;
+ I32 len;
+ char *t_key, *t_value;
+CODE:
+ t_HV = (HV *)SvRV(components);
+ t_GHash = g_hash_table_new(g_str_hash, g_str_equal);
+
+ for (t_HE = hv_iternext(t_HV); t_HE != NULL; t_HE = hv_iternext(t_HV) ) {
+ t_key = hv_iterkey(t_HE, &len);
+ t_SV = *hv_fetch(t_HV, t_key, len, 0);
+ t_value = SvPV(t_SV, PL_na);
+
+ g_hash_table_insert(t_GHash, t_key, t_value);
+ }
+
+ RETVAL = gaim_chat_new(account, alias, t_GHash);
+OUTPUT:
+ RETVAL
+
+MODULE = Gaim::BuddyList PACKAGE = Gaim::BuddyList::Buddy PREFIX = gaim_buddy_
+PROTOTYPES: ENABLE
+
+Gaim::BuddyList::Buddy
+gaim_buddy_new(account, screenname, alias)
+ Gaim::Account account
+ const char *screenname
+ const char *alias
+
+const char *
+gaim_buddy_get_server_alias(buddy)
+ Gaim::BuddyList::Buddy buddy
+
+void
+gaim_buddy_set_icon(buddy, icon)
+ Gaim::BuddyList::Buddy buddy
+ Gaim::Buddy::Icon icon
+
+Gaim::Account
+gaim_buddy_get_account(buddy)
+ Gaim::BuddyList::Buddy buddy
+
+Gaim::BuddyList::Group
+gaim_buddy_get_group(buddy)
+ Gaim::BuddyList::Buddy buddy
+
+const char *
+gaim_buddy_get_name(buddy)
+ Gaim::BuddyList::Buddy buddy
+
+Gaim::Buddy::Icon
+gaim_buddy_get_icon(buddy)
+ Gaim::BuddyList::Buddy buddy
+
+Gaim::BuddyList::Contact
+gaim_buddy_get_contact(buddy)
+ Gaim::BuddyList::Buddy buddy
+
+Gaim::Presence
+gaim_buddy_get_presence(buddy)
+ Gaim::BuddyList::Buddy buddy
+
+const char *
+gaim_buddy_get_alias_only(buddy)
+ Gaim::BuddyList::Buddy buddy
+
+const char *
+gaim_buddy_get_contact_alias(buddy)
+ Gaim::BuddyList::Buddy buddy
+
+const char *
+gaim_buddy_get_local_alias(buddy)
+ Gaim::BuddyList::Buddy buddy
+
+const char *
+gaim_buddy_get_alias(buddy)
+ Gaim::BuddyList::Buddy buddy
diff --git a/libpurple/plugins/perl/common/Cipher.xs b/libpurple/plugins/perl/common/Cipher.xs
new file mode 100644
index 0000000000..64f0a1e59d
--- /dev/null
+++ b/libpurple/plugins/perl/common/Cipher.xs
@@ -0,0 +1,157 @@
+#include "module.h"
+
+MODULE = Gaim::Cipher PACKAGE = Gaim::Cipher PREFIX = gaim_cipher_
+PROTOTYPES: ENABLE
+
+const gchar *
+gaim_cipher_get_name(cipher)
+ Gaim::Cipher cipher
+
+guint
+gaim_cipher_get_capabilities(cipher)
+ Gaim::Cipher cipher
+
+gboolean
+gaim_cipher_digest_region(name, data, data_len, in_len, digest, out_len)
+ const gchar * name
+ const guchar * data
+ size_t data_len
+ size_t in_len
+ guchar &digest
+ size_t * out_len
+
+MODULE = Gaim::Cipher PACKAGE = Gaim::Ciphers PREFIX = gaim_ciphers_
+PROTOTYPES: ENABLE
+
+Gaim::Cipher
+gaim_ciphers_find_cipher(name)
+ gchar * name
+
+Gaim::Cipher
+gaim_ciphers_register_cipher(name, ops)
+ gchar * name
+ Gaim::Cipher::Ops ops
+
+gboolean
+gaim_ciphers_unregister_cipher(cipher)
+ Gaim::Cipher cipher
+
+void
+gaim_ciphers_get_ciphers()
+PREINIT:
+ GList *l;
+PPCODE:
+ for (l = gaim_ciphers_get_ciphers(); l != NULL; l = l->next) {
+ XPUSHs(sv_2mortal(gaim_perl_bless_object(l->data, "Gaim::Cipher")));
+ }
+
+Gaim::Handle
+gaim_ciphers_get_handle()
+
+void
+gaim_ciphers_init()
+
+void
+gaim_ciphers_uninit()
+
+MODULE = Gaim::Cipher PACKAGE = Gaim::Cipher::Context PREFIX = gaim_cipher_context_
+PROTOTYPES: ENABLE
+
+void
+gaim_cipher_context_set_option(context, name, value)
+ Gaim::Cipher::Context context
+ gchar *name
+ gpointer value
+
+gpointer
+gaim_cipher_context_get_option(context, name)
+ Gaim::Cipher::Context context
+ gchar *name
+
+Gaim::Cipher::Context
+gaim_cipher_context_new(cipher, extra)
+ Gaim::Cipher cipher
+ void *extra
+
+Gaim::Cipher::Context
+gaim_cipher_context_new_by_name(name, extra)
+ gchar *name
+ void *extra
+
+void
+gaim_cipher_context_reset(context, extra)
+ Gaim::Cipher::Context context
+ gpointer extra
+
+void
+gaim_cipher_context_destroy(context)
+ Gaim::Cipher::Context context
+
+void
+gaim_cipher_context_set_iv(context, iv, len)
+ Gaim::Cipher::Context context
+ guchar * iv
+ size_t len
+
+void
+gaim_cipher_context_append(context, data, len)
+ Gaim::Cipher::Context context
+ guchar * data
+ size_t len
+
+gboolean
+gaim_cipher_context_digest(context, in_len, digest, out_len)
+ Gaim::Cipher::Context context
+ size_t in_len
+ guchar &digest
+ size_t &out_len
+
+gboolean
+gaim_cipher_context_digest_to_str(context, in_len, digest_s, out_len)
+ Gaim::Cipher::Context context
+ size_t in_len
+ gchar &digest_s
+ size_t &out_len
+
+gint
+gaim_cipher_context_encrypt(context, data, len, output, outlen)
+ Gaim::Cipher::Context context
+ guchar &data
+ size_t len
+ guchar &output
+ size_t &outlen
+
+gint
+gaim_cipher_context_decrypt(context, data, len, output, outlen)
+ Gaim::Cipher::Context context
+ guchar &data
+ size_t len
+ guchar &output
+ size_t &outlen
+
+void
+gaim_cipher_context_set_salt(context, salt)
+ Gaim::Cipher::Context context
+ guchar *salt
+
+size_t
+gaim_cipher_context_get_salt_size(context)
+ Gaim::Cipher::Context context
+
+void
+gaim_cipher_context_set_key(context, key)
+ Gaim::Cipher::Context context
+ guchar *key
+
+size_t
+gaim_cipher_context_get_key_size(context)
+ Gaim::Cipher::Context context
+
+void
+gaim_cipher_context_set_data(context, data)
+ Gaim::Cipher::Context context
+ gpointer data
+
+gpointer
+gaim_cipher_context_get_data(context)
+ Gaim::Cipher::Context context
diff --git a/libpurple/plugins/perl/common/Cmds.xs b/libpurple/plugins/perl/common/Cmds.xs
new file mode 100644
index 0000000000..0aa87f6060
--- /dev/null
+++ b/libpurple/plugins/perl/common/Cmds.xs
@@ -0,0 +1,49 @@
+#include "module.h"
+#include "../perl-handlers.h"
+
+MODULE = Gaim::Cmd PACKAGE = Gaim::Cmd PREFIX = gaim_cmd_
+PROTOTYPES: ENABLE
+
+void
+gaim_cmd_help(conv, command)
+ Gaim::Conversation conv
+ const gchar *command
+PREINIT:
+ GList *l;
+PPCODE:
+ for (l = gaim_cmd_help(conv, command); l != NULL; l = l->next) {
+ XPUSHs(sv_2mortal(newSVpv(l->data, 0)));
+ }
+
+void
+gaim_cmd_list(conv)
+ Gaim::Conversation conv
+PREINIT:
+ GList *l;
+PPCODE:
+ for (l = gaim_cmd_list(conv); l != NULL; l = l->next) {
+ XPUSHs(sv_2mortal(newSVpv(l->data, 0)));
+ }
+
+Gaim::Cmd::Id
+gaim_cmd_register(plugin, command, args, priority, flag, prpl_id, func, helpstr, data = 0)
+ Gaim::Plugin plugin
+ const gchar *command
+ const gchar *args
+ Gaim::Cmd::Priority priority
+ Gaim::Cmd::Flag flag
+ const gchar *prpl_id
+ SV *func
+ const gchar *helpstr
+ SV *data
+CODE:
+ RETVAL = gaim_perl_cmd_register(plugin, command, args, priority, flag,
+ prpl_id, func, helpstr, data);
+OUTPUT:
+ RETVAL
+
+void
+gaim_cmd_unregister(id)
+ Gaim::Cmd::Id id
+CODE:
+ gaim_perl_cmd_unregister(id);
diff --git a/libpurple/plugins/perl/common/Connection.xs b/libpurple/plugins/perl/common/Connection.xs
new file mode 100644
index 0000000000..bae0d030dc
--- /dev/null
+++ b/libpurple/plugins/perl/common/Connection.xs
@@ -0,0 +1,89 @@
+#include "module.h"
+
+MODULE = Gaim::Connection PACKAGE = Gaim::Connection PREFIX = gaim_connection_
+PROTOTYPES: ENABLE
+
+Gaim::Account
+gaim_connection_get_account(gc)
+ Gaim::Connection gc
+
+const char *
+gaim_connection_get_password(gc)
+ Gaim::Connection gc
+
+const char *
+gaim_connection_get_display_name(gc)
+ Gaim::Connection gc
+
+void
+gaim_connection_notice(gc, text)
+ Gaim::Connection gc
+ const char *text
+
+void
+gaim_connection_error(gc, reason)
+ Gaim::Connection gc
+ const char *reason
+
+void
+gaim_connection_destroy(gc)
+ Gaim::Connection gc
+
+void
+gaim_connection_set_state(gc, state)
+ Gaim::Connection gc
+ Gaim::ConnectionState state
+
+void
+gaim_connection_set_account(gc, account)
+ Gaim::Connection gc
+ Gaim::Account account
+
+void
+gaim_connection_set_display_name(gc, name)
+ Gaim::Connection gc
+ const char *name
+
+Gaim::ConnectionState
+gaim_connection_get_state(gc)
+ Gaim::Connection gc
+
+MODULE = Gaim::Connection PACKAGE = Gaim::Connections PREFIX = gaim_connections_
+PROTOTYPES: ENABLE
+
+void
+gaim_connections_disconnect_all()
+
+void
+gaim_connections_get_all()
+PREINIT:
+ GList *l;
+PPCODE:
+ for (l = gaim_connections_get_all(); l != NULL; l = l->next) {
+ XPUSHs(sv_2mortal(gaim_perl_bless_object(l->data, "Gaim::Connection")));
+ }
+
+void
+gaim_connections_get_connecting()
+PREINIT:
+ GList *l;
+PPCODE:
+ for (l = gaim_connections_get_connecting(); l != NULL; l = l->next) {
+ XPUSHs(sv_2mortal(gaim_perl_bless_object(l->data, "Gaim::Connection")));
+ }
+
+void
+gaim_connections_set_ui_ops(ops)
+ Gaim::Connection::UiOps ops
+
+Gaim::Connection::UiOps
+gaim_connections_get_ui_ops()
+
+void
+gaim_connections_init()
+
+void
+gaim_connections_uninit()
+
+Gaim::Handle
+gaim_connections_get_handle()
diff --git a/libpurple/plugins/perl/common/Conversation.xs b/libpurple/plugins/perl/common/Conversation.xs
new file mode 100644
index 0000000000..7c2ef3187c
--- /dev/null
+++ b/libpurple/plugins/perl/common/Conversation.xs
@@ -0,0 +1,402 @@
+#include "module.h"
+
+MODULE = Gaim::Conversation PACKAGE = Gaim PREFIX = gaim_
+PROTOTYPES: ENABLE
+
+void
+gaim_get_ims()
+PREINIT:
+ GList *l;
+PPCODE:
+ for (l = gaim_get_ims(); l != NULL; l = l->next) {
+ XPUSHs(sv_2mortal(gaim_perl_bless_object(l->data, "Gaim::Conversation")));
+ }
+
+void
+gaim_get_conversations()
+PREINIT:
+ GList *l;
+PPCODE:
+ for (l = gaim_get_conversations(); l != NULL; l = l->next) {
+ XPUSHs(sv_2mortal(gaim_perl_bless_object(l->data, "Gaim::Conversation")));
+ }
+
+void
+gaim_get_chats()
+PREINIT:
+ GList *l;
+PPCODE:
+ for (l = gaim_get_chats(); l != NULL; l = l->next) {
+ XPUSHs(sv_2mortal(gaim_perl_bless_object(l->data, "Gaim::Conversation")));
+ }
+
+MODULE = Gaim::Conversation PACKAGE = Gaim::Conversations PREFIX = gaim_conversations_
+PROTOTYPES: ENABLE
+
+Gaim::Handle
+gaim_conversations_get_handle()
+
+void
+gaim_conversations_init()
+
+void
+gaim_conversations_uninit()
+
+MODULE = Gaim::Conversation PACKAGE = Gaim::Conversation PREFIX = gaim_conversation_
+PROTOTYPES: ENABLE
+
+void
+gaim_conversation_destroy(conv)
+ Gaim::Conversation conv
+
+Gaim::ConversationType
+gaim_conversation_get_type(conv)
+ Gaim::Conversation conv
+
+Gaim::Account
+gaim_conversation_get_account(conv)
+ Gaim::Conversation conv
+
+Gaim::Connection
+gaim_conversation_get_gc(conv)
+ Gaim::Conversation conv
+
+void
+gaim_conversation_set_title(conv, title);
+ Gaim::Conversation conv
+ const char * title
+
+const char *
+gaim_conversation_get_title(conv)
+ Gaim::Conversation conv
+
+void
+gaim_conversation_autoset_title(conv)
+ Gaim::Conversation conv
+
+void
+gaim_conversation_set_name(conv, name)
+ Gaim::Conversation conv
+ const char *name
+
+const char *
+gaim_conversation_get_name(conv)
+ Gaim::Conversation conv
+
+void
+gaim_conversation_set_logging(conv, log)
+ Gaim::Conversation conv
+ gboolean log
+
+gboolean
+gaim_conversation_is_logging(conv)
+ Gaim::Conversation conv
+
+Gaim::Conversation::IM
+gaim_conversation_get_im_data(conv)
+ Gaim::Conversation conv
+
+Gaim::Conversation::Chat
+gaim_conversation_get_chat_data(conv)
+ Gaim::Conversation conv
+
+gpointer
+gaim_conversation_get_data(conv, key)
+ Gaim::Conversation conv
+ const char * key
+
+Gaim::ConnectionFlags
+gaim_conversation_get_features(conv)
+ Gaim::Conversation conv
+
+gboolean
+gaim_conversation_has_focus(conv)
+ Gaim::Conversation conv
+
+void
+gaim_conversation_update(conv, type)
+ Gaim::Conversation conv
+ Gaim::ConvUpdateType type
+
+Gaim::Conversation
+gaim_conversation_new(class, type, account, name)
+ Gaim::ConversationType type
+ Gaim::Account account
+ const char *name
+ C_ARGS:
+ type, account, name
+
+void
+gaim_conversation_set_account(conv, account);
+ Gaim::Conversation conv
+ Gaim::Account account
+
+MODULE = Gaim::Conversation PACKAGE = Gaim::Conversation::IM PREFIX = gaim_conv_im_
+PROTOTYPES: ENABLE
+
+Gaim::Conversation
+gaim_conv_im_get_conversation(im)
+ Gaim::Conversation::IM im
+
+void
+gaim_conv_im_set_icon(im, icon)
+ Gaim::Conversation::IM im
+ Gaim::Buddy::Icon icon
+
+Gaim::Buddy::Icon
+gaim_conv_im_get_icon(im)
+ Gaim::Conversation::IM im
+
+void
+gaim_conv_im_set_typing_state(im, state)
+ Gaim::Conversation::IM im
+ Gaim::TypingState state
+
+Gaim::TypingState
+gaim_conv_im_get_typing_state(im)
+ Gaim::Conversation::IM im
+
+void
+gaim_conv_im_start_typing_timeout(im, timeout)
+ Gaim::Conversation::IM im
+ int timeout
+
+void
+gaim_conv_im_stop_typing_timeout(im)
+ Gaim::Conversation::IM im
+
+guint
+gaim_conv_im_get_typing_timeout(im)
+ Gaim::Conversation::IM im
+
+void
+gaim_conv_im_set_type_again(im, val)
+ Gaim::Conversation::IM im
+ time_t val
+
+time_t
+gaim_conv_im_get_type_again(im)
+ Gaim::Conversation::IM im
+
+void
+gaim_conv_im_start_send_typed_timeout(im)
+ Gaim::Conversation::IM im
+
+void
+gaim_conv_im_stop_send_typed_timeout(im)
+ Gaim::Conversation::IM im
+
+guint
+gaim_conv_im_get_send_typed_timeout(im)
+ Gaim::Conversation::IM im
+
+void
+gaim_conv_im_update_typing(im)
+ Gaim::Conversation::IM im
+
+void
+gaim_conv_im_send(im, message)
+ Gaim::Conversation::IM im
+ const char *message
+
+void
+gaim_conv_im_write(im, who, message, flags, mtime)
+ Gaim::Conversation::IM im
+ const char *who
+ const char *message
+ Gaim::MessageFlags flags
+ time_t mtime
+
+MODULE = Gaim::Conversation PACKAGE = Gaim::Conversation PREFIX = gaim_conv_
+PROTOTYPES: ENABLE
+
+gboolean
+gaim_conv_present_error(who, account, what)
+ const char *who
+ Gaim::Account account
+ const char *what
+
+void
+gaim_conv_custom_smiley_close(conv, smile)
+ Gaim::Conversation conv
+ const char *smile
+
+MODULE = Gaim::Conversation PACKAGE = Gaim::Conversation::Chat PREFIX = gaim_conv_chat_
+PROTOTYPES: ENABLE
+
+Gaim::Conversation
+gaim_conv_chat_get_conversation(chat)
+ Gaim::Conversation::Chat chat
+
+void
+gaim_conv_chat_set_users(chat, users)
+ Gaim::Conversation::Chat chat
+ SV * users
+PREINIT:
+ GList *l, *t_GL;
+ int i, t_len;
+PPCODE:
+ t_GL = NULL;
+ t_len = av_len((AV *)SvRV(users));
+
+ for (i = 0; i < t_len; i++) {
+ STRLEN t_sl;
+ t_GL = g_list_append(t_GL, SvPV(*av_fetch((AV *)SvRV(users), i, 0), t_sl));
+ }
+
+ for (l = gaim_conv_chat_set_users(chat, t_GL); l != NULL; l = l->next) {
+ XPUSHs(sv_2mortal(gaim_perl_bless_object(l->data, "Gaim::ListEntry")));
+ }
+
+void
+gaim_conv_chat_get_users(chat)
+ Gaim::Conversation::Chat chat
+PREINIT:
+ GList *l;
+PPCODE:
+ for (l = gaim_conv_chat_get_users(chat); l != NULL; l = l->next) {
+ XPUSHs(sv_2mortal(gaim_perl_bless_object(l->data, "Gaim::ListEntry")));
+ }
+
+void
+gaim_conv_chat_ignore(chat, name)
+ Gaim::Conversation::Chat chat
+ const char *name
+
+void
+gaim_conv_chat_unignore(chat, name)
+ Gaim::Conversation::Chat chat
+ const char *name
+
+void
+gaim_conv_chat_set_ignored(chat, ignored)
+ Gaim::Conversation::Chat chat
+ SV * ignored
+PREINIT:
+ GList *l, *t_GL;
+ int i, t_len;
+PPCODE:
+ t_GL = NULL;
+ t_len = av_len((AV *)SvRV(ignored));
+
+ for (i = 0; i < t_len; i++) {
+ STRLEN t_sl;
+ t_GL = g_list_append(t_GL, SvPV(*av_fetch((AV *)SvRV(ignored), i, 0), t_sl));
+ }
+
+ for (l = gaim_conv_chat_set_ignored(chat, t_GL); l != NULL; l = l->next) {
+ XPUSHs(sv_2mortal(gaim_perl_bless_object(l->data, "Gaim::ListEntry")));
+ }
+
+void
+gaim_conv_chat_get_ignored(chat)
+ Gaim::Conversation::Chat chat
+PREINIT:
+ GList *l;
+PPCODE:
+ for (l = gaim_conv_chat_get_ignored(chat); l != NULL; l = l->next) {
+ XPUSHs(sv_2mortal(gaim_perl_bless_object(l->data, "Gaim::ListEntry")));
+ }
+
+const char *
+gaim_conv_chat_get_topic(chat)
+ Gaim::Conversation::Chat chat
+
+void
+gaim_conv_chat_set_id(chat, id)
+ Gaim::Conversation::Chat chat
+ int id
+
+int
+gaim_conv_chat_get_id(chat)
+ Gaim::Conversation::Chat chat
+
+void
+gaim_conv_chat_send(chat, message)
+ Gaim::Conversation::Chat chat
+ const char * message
+
+void
+gaim_conv_chat_write(chat, who, message, flags, mtime)
+ Gaim::Conversation::Chat chat
+ const char *who
+ const char *message
+ Gaim::MessageFlags flags
+ time_t mtime
+
+void
+gaim_conv_chat_add_users(chat, users, extra_msgs, flags, new_arrivals)
+ Gaim::Conversation::Chat chat
+ SV * users
+ SV * extra_msgs
+ SV * flags
+ gboolean new_arrivals
+PREINIT:
+ GList *t_GL_users, *t_GL_extra_msgs, *t_GL_flags;
+ int i, t_len;
+PPCODE:
+ t_GL_users = NULL;
+ t_len = av_len((AV *)SvRV(users));
+
+ for (i = 0; i < t_len; i++) {
+ STRLEN t_sl;
+ t_GL_users = g_list_append(t_GL_users, SvPV(*av_fetch((AV *)SvRV(users), i, 0), t_sl));
+ }
+
+ t_GL_flags = NULL;
+ t_len = av_len((AV *)SvRV(flags));
+
+ for (i = 0; i < t_len; i++) {
+ STRLEN t_sl;
+ t_GL_flags = g_list_append(t_GL_flags, SvPV(*av_fetch((AV *)SvRV(flags), i, 0), t_sl));
+ }
+
+ t_GL_extra_msgs = NULL;
+ t_len = av_len((AV *)SvRV(extra_msgs));
+
+ for (i = 0; i < t_len; i++) {
+ STRLEN t_sl;
+ t_GL_extra_msgs = g_list_append(t_GL_extra_msgs, SvPV(*av_fetch((AV *)SvRV(extra_msgs), i, 0), t_sl));
+ }
+
+ gaim_conv_chat_add_users(chat, t_GL_users, t_GL_extra_msgs, t_GL_flags, new_arrivals);
+
+gboolean
+gaim_conv_chat_find_user(chat, user)
+ Gaim::Conversation::Chat chat
+ const char * user
+
+void gaim_conv_chat_clear_users(chat)
+ Gaim::Conversation::Chat chat
+
+void gaim_conv_chat_set_nick(chat, nick)
+ Gaim::Conversation::Chat chat
+ const char * nick
+
+const char *
+gaim_conv_chat_get_nick(chat)
+ Gaim::Conversation::Chat chat
+
+Gaim::Conversation
+gaim_find_chat(gc, id)
+ Gaim::Connection gc
+ int id
+
+void gaim_conv_chat_left(chat)
+ Gaim::Conversation::Chat chat
+
+gboolean gaim_conv_chat_has_left(chat)
+ Gaim::Conversation::Chat chat
+
+Gaim::Conversation::ChatBuddy
+gaim_conv_chat_cb_find(chat, name)
+ Gaim::Conversation::Chat chat
+ const char *name
+
+const char *
+gaim_conv_chat_cb_get_name(cb)
+ Gaim::Conversation::ChatBuddy cb
+
+void
+gaim_conv_chat_cb_destroy(cb);
+ Gaim::Conversation::ChatBuddy cb
diff --git a/libpurple/plugins/perl/common/Core.xs b/libpurple/plugins/perl/common/Core.xs
new file mode 100644
index 0000000000..15db1632a2
--- /dev/null
+++ b/libpurple/plugins/perl/common/Core.xs
@@ -0,0 +1,28 @@
+#include "module.h"
+
+MODULE = Gaim::Core PACKAGE = Gaim::Core PREFIX = gaim_core_
+PROTOTYPES: ENABLE
+
+gboolean
+gaim_core_quit_cb()
+PPCODE:
+ /* The argument to gaim_core_quit_cb is not used,
+ * so there's little point in requiring it on the
+ * Perl side. */
+ RETVAL = gaim_core_quit_cb(NULL);
+ ST(0) = boolSV(RETVAL);
+ sv_2mortal(ST(0));
+
+const char *
+gaim_core_get_version()
+
+const char *
+gaim_core_get_ui()
+
+void
+gaim_core_set_ui_ops(ops)
+ Gaim::Core::UiOps ops
+
+Gaim::Core::UiOps
+gaim_core_get_ui_ops()
+
diff --git a/libpurple/plugins/perl/common/Debug.xs b/libpurple/plugins/perl/common/Debug.xs
new file mode 100644
index 0000000000..d73e55f9e3
--- /dev/null
+++ b/libpurple/plugins/perl/common/Debug.xs
@@ -0,0 +1,54 @@
+#include "module.h"
+
+MODULE = Gaim::Debug PACKAGE = Gaim::Debug PREFIX = gaim_debug_
+PROTOTYPES: ENABLE
+
+void
+gaim_debug(level, category, string)
+ Gaim::DebugLevel level
+ const char *category
+ const char *string
+CODE:
+ gaim_debug(level, category, "%s", string);
+
+void
+gaim_debug_misc(category, string)
+ const char *category
+ const char *string
+CODE:
+ gaim_debug_misc(category, "%s", string);
+
+void
+gaim_debug_info(category, string)
+ const char *category
+ const char *string
+CODE:
+ gaim_debug_info(category, "%s", string);
+
+void
+gaim_debug_warning(category, string)
+ const char *category
+ const char *string
+CODE:
+ gaim_debug_warning(category, "%s", string);
+
+void
+gaim_debug_error(category, string)
+ const char *category
+ const char *string
+CODE:
+ gaim_debug_error(category, "%s", string);
+
+void
+gaim_debug_fatal(category, string)
+ const char *category
+ const char *string
+CODE:
+ gaim_debug_fatal(category, "%s", string);
+
+void
+gaim_debug_set_enabled(enabled)
+ gboolean enabled
+
+gboolean
+gaim_debug_is_enabled()
diff --git a/libpurple/plugins/perl/common/FT.xs b/libpurple/plugins/perl/common/FT.xs
new file mode 100644
index 0000000000..c4232fbcc8
--- /dev/null
+++ b/libpurple/plugins/perl/common/FT.xs
@@ -0,0 +1,168 @@
+#include "module.h"
+
+MODULE = Gaim::Xfer PACKAGE = Gaim::Xfer PREFIX = gaim_xfer_
+PROTOTYPES: ENABLE
+
+Gaim::Xfer
+gaim_xfer_new(class, account, type, who)
+ Gaim::Account account
+ Gaim::XferType type
+ const char *who
+ C_ARGS:
+ account, type, who
+
+void
+gaim_xfer_add(xfer)
+ Gaim::Xfer xfer
+
+void
+gaim_xfer_cancel_local(xfer)
+ Gaim::Xfer xfer
+
+void
+gaim_xfer_cancel_remote(xfer)
+ Gaim::Xfer xfer
+
+void
+gaim_xfer_end(xfer)
+ Gaim::Xfer xfer
+
+void
+gaim_xfer_error(type, account, who, msg)
+ Gaim::XferType type
+ Gaim::Account account
+ const char *who
+ const char *msg
+
+Gaim::Account
+gaim_xfer_get_account(xfer)
+ Gaim::Xfer xfer
+
+size_t
+gaim_xfer_get_bytes_remaining(xfer)
+ Gaim::Xfer xfer
+
+size_t
+gaim_xfer_get_bytes_sent(xfer)
+ Gaim::Xfer xfer
+
+const char *
+gaim_xfer_get_filename(xfer)
+ Gaim::Xfer xfer
+
+const char *
+gaim_xfer_get_local_filename(xfer)
+ Gaim::Xfer xfer
+
+unsigned int
+gaim_xfer_get_local_port(xfer)
+ Gaim::Xfer xfer
+
+double
+gaim_xfer_get_progress(xfer)
+ Gaim::Xfer xfer
+
+const char *
+gaim_xfer_get_remote_ip(xfer)
+ Gaim::Xfer xfer
+
+unsigned int
+gaim_xfer_get_remote_port(xfer)
+ Gaim::Xfer xfer
+
+size_t
+gaim_xfer_get_size(xfer)
+ Gaim::Xfer xfer
+
+Gaim::XferStatusType
+gaim_xfer_get_status(xfer)
+ Gaim::Xfer xfer
+
+Gaim::XferType
+gaim_xfer_get_type(xfer)
+ Gaim::Xfer xfer
+
+Gaim::XferUiOps
+gaim_xfer_get_ui_ops(xfer)
+ Gaim::Xfer xfer
+
+gboolean
+gaim_xfer_is_canceled(xfer)
+ Gaim::Xfer xfer
+
+gboolean
+gaim_xfer_is_completed(xfer)
+ Gaim::Xfer xfer
+
+ssize_t
+gaim_xfer_read(xfer, buffer)
+ Gaim::Xfer xfer
+ guchar **buffer
+
+void
+gaim_xfer_ref(xfer)
+ Gaim::Xfer xfer
+
+void
+gaim_xfer_request(xfer)
+ Gaim::Xfer xfer
+
+void
+gaim_xfer_request_accepted(xfer, filename)
+ Gaim::Xfer xfer
+ const char *filename
+
+void
+gaim_xfer_request_denied(xfer)
+ Gaim::Xfer xfer
+
+void
+gaim_xfer_set_completed(xfer, completed)
+ Gaim::Xfer xfer
+ gboolean completed
+
+void
+gaim_xfer_set_filename(xfer, filename)
+ Gaim::Xfer xfer
+ const char *filename
+
+void
+gaim_xfer_set_local_filename(xfer, filename)
+ Gaim::Xfer xfer
+ const char *filename
+
+void
+gaim_xfer_set_message(xfer, message)
+ Gaim::Xfer xfer
+ const char *message
+
+void
+gaim_xfer_set_size(xfer, size)
+ Gaim::Xfer xfer
+ size_t size
+
+void
+gaim_xfer_unref(xfer)
+ Gaim::Xfer xfer
+
+void
+gaim_xfer_update_progress(xfer)
+ Gaim::Xfer xfer
+
+ssize_t
+gaim_xfer_write(xfer, buffer, size)
+ Gaim::Xfer xfer
+ const guchar *buffer
+ size_t size
+
+MODULE = Gaim::Xfer PACKAGE = Gaim::Xfers PREFIX = gaim_xfers_
+PROTOTYPES: ENABLE
+
+Gaim::XferUiOps
+gaim_xfers_get_ui_ops()
+
+
+void
+gaim_xfers_set_ui_ops(ops)
+ Gaim::XferUiOps ops
+
diff --git a/libpurple/plugins/perl/common/Gaim.pm b/libpurple/plugins/perl/common/Gaim.pm
new file mode 100644
index 0000000000..c2ca5b85fa
--- /dev/null
+++ b/libpurple/plugins/perl/common/Gaim.pm
@@ -0,0 +1,131 @@
+package Gaim;
+
+use 5.008;
+use strict;
+use warnings;
+use Carp;
+
+require Exporter;
+use AutoLoader;
+
+our @ISA = qw(Exporter);
+
+# Items to export into callers namespace by default. Note: do not export
+# names by default without a very good reason. Use EXPORT_OK instead.
+# Do not simply export all your public functions/methods/constants.
+
+# This allows declaration use Gaim ':all';
+# If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
+# will save memory.
+our %EXPORT_TAGS = ( 'all' => [ qw(
+
+) ] );
+
+our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
+
+our @EXPORT = qw(
+
+);
+
+our $VERSION = '0.01';
+
+sub AUTOLOAD {
+ # This AUTOLOAD is used to 'autoload' constants from the constant()
+ # XS function.
+
+ my $constname;
+ our $AUTOLOAD;
+ ($constname = $AUTOLOAD) =~ s/.*:://;
+ croak "&Gaim::constant not defined" if $constname eq 'constant';
+ my ($error, $val) = constant($constname);
+ if ($error) { croak $error; }
+ {
+ no strict 'refs';
+
+ *$AUTOLOAD = sub { $val };
+ }
+
+ goto &$AUTOLOAD;
+}
+
+require XSLoader;
+XSLoader::load('Gaim', $VERSION);
+
+# Preloaded methods go here.
+
+1;
+__END__
+
+=head1 NAME
+
+Gaim - Perl extension the Gaim instant messenger.
+
+=head1 SYNOPSIS
+
+ use Gaim;
+
+=head1 ABSTRACT
+
+ This module provides the interface for using perl scripts as plugins
+ in Gaim.
+
+=head1 DESCRIPTION
+
+This module provides the interface for using perl scripts as plugins
+in Gaim. With this, developers can write perl scripts that can be
+loaded in Gaim as plugins. The scripts can interact with IMs, chats,
+accounts, the buddy list, gaim signals, and more.
+
+The API for the perl interface is very similar to that of the Gaim C
+API, which can be viewed at http://gaim.sourceforge.net/api/ or in
+the header files in the Gaim source tree.
+
+=head1 FUNCTIONS
+
+=over
+
+=item @accounts = Gaim::accounts
+
+Returns a list of all accounts, online or offline.
+
+=item @chats = Gaim::chats
+
+Returns a list of all chats currently open.
+
+=item @connections = Gaim::connections
+
+Returns a list of all active connections.
+
+=item @conversations = Gaim::conversations
+
+Returns a list of all conversations, both IM and chat, currently open.
+
+=item @conv_windows = Gaim::conv_windows
+
+Returns a list of all conversation windows currently open.
+
+=item @ims = Gaim::ims
+
+Returns a list of all instant messages currently open.
+
+=back
+
+=head1 SEE ALSO
+
+Gaim C API documentation - http//gaim.sourceforge.net/api/
+
+Gaim website - http://gaim.sourceforge.net/
+
+=head1 AUTHOR
+
+Christian Hammond, E<lt>chipx86@gnupdate.orgE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2003 by Christian Hammond
+
+This library is free software; you can redistribute it and/or modify
+it under the terms of the General Public License (GPL). For
+more information, see http://www.fsf.org/licenses/gpl.txt
+
+=cut
diff --git a/libpurple/plugins/perl/common/Gaim.xs b/libpurple/plugins/perl/common/Gaim.xs
new file mode 100644
index 0000000000..49a0f6b9a1
--- /dev/null
+++ b/libpurple/plugins/perl/common/Gaim.xs
@@ -0,0 +1,99 @@
+#include "module.h"
+#include "../perl-handlers.h"
+#include "const-c.inc"
+
+/* Prototypes for the BOOT section below. */
+GAIM_PERL_BOOT_PROTO(Account);
+GAIM_PERL_BOOT_PROTO(Account__Option);
+GAIM_PERL_BOOT_PROTO(Buddy__Icon);
+GAIM_PERL_BOOT_PROTO(BuddyList);
+GAIM_PERL_BOOT_PROTO(Cipher);
+GAIM_PERL_BOOT_PROTO(Cmd);
+GAIM_PERL_BOOT_PROTO(Connection);
+GAIM_PERL_BOOT_PROTO(Conversation);
+GAIM_PERL_BOOT_PROTO(Core);
+GAIM_PERL_BOOT_PROTO(Debug);
+GAIM_PERL_BOOT_PROTO(Xfer);
+GAIM_PERL_BOOT_PROTO(ImgStore);
+GAIM_PERL_BOOT_PROTO(Log);
+GAIM_PERL_BOOT_PROTO(Network);
+GAIM_PERL_BOOT_PROTO(Notify);
+GAIM_PERL_BOOT_PROTO(Plugin);
+GAIM_PERL_BOOT_PROTO(PluginPref);
+GAIM_PERL_BOOT_PROTO(Pounce);
+GAIM_PERL_BOOT_PROTO(Prefs);
+GAIM_PERL_BOOT_PROTO(Privacy);
+GAIM_PERL_BOOT_PROTO(Proxy);
+GAIM_PERL_BOOT_PROTO(Prpl);
+GAIM_PERL_BOOT_PROTO(Request);
+GAIM_PERL_BOOT_PROTO(Roomlist);
+GAIM_PERL_BOOT_PROTO(SSL);
+GAIM_PERL_BOOT_PROTO(SavedStatus);
+GAIM_PERL_BOOT_PROTO(Serv);
+GAIM_PERL_BOOT_PROTO(Signal);
+GAIM_PERL_BOOT_PROTO(Sound);
+GAIM_PERL_BOOT_PROTO(Status);
+GAIM_PERL_BOOT_PROTO(Stringref);
+GAIM_PERL_BOOT_PROTO(Util);
+GAIM_PERL_BOOT_PROTO(XMLNode);
+
+MODULE = Gaim PACKAGE = Gaim PREFIX = gaim_
+PROTOTYPES: ENABLE
+
+INCLUDE: const-xs.inc
+
+BOOT:
+ GAIM_PERL_BOOT(Account);
+ GAIM_PERL_BOOT(Account__Option);
+ GAIM_PERL_BOOT(Buddy__Icon);
+ GAIM_PERL_BOOT(BuddyList);
+ GAIM_PERL_BOOT(Cipher);
+ GAIM_PERL_BOOT(Cmd);
+ GAIM_PERL_BOOT(Connection);
+ GAIM_PERL_BOOT(Conversation);
+ GAIM_PERL_BOOT(Core);
+ GAIM_PERL_BOOT(Debug);
+ GAIM_PERL_BOOT(Xfer);
+ GAIM_PERL_BOOT(ImgStore);
+ GAIM_PERL_BOOT(Log);
+ GAIM_PERL_BOOT(Network);
+ GAIM_PERL_BOOT(Notify);
+ GAIM_PERL_BOOT(Plugin);
+ GAIM_PERL_BOOT(PluginPref);
+ GAIM_PERL_BOOT(Pounce);
+ GAIM_PERL_BOOT(Prefs);
+ GAIM_PERL_BOOT(Privacy);
+ GAIM_PERL_BOOT(Proxy);
+ GAIM_PERL_BOOT(Prpl);
+ GAIM_PERL_BOOT(Request);
+ GAIM_PERL_BOOT(Roomlist);
+ GAIM_PERL_BOOT(SSL);
+ GAIM_PERL_BOOT(SavedStatus);
+ GAIM_PERL_BOOT(Serv);
+ GAIM_PERL_BOOT(Signal);
+ GAIM_PERL_BOOT(Sound);
+ GAIM_PERL_BOOT(Status);
+ GAIM_PERL_BOOT(Stringref);
+ GAIM_PERL_BOOT(Util);
+ GAIM_PERL_BOOT(XMLNode);
+
+void
+timeout_add(plugin, seconds, callback, data = 0)
+ Gaim::Plugin plugin
+ int seconds
+ SV *callback
+ SV *data
+CODE:
+ gaim_perl_timeout_add(plugin, seconds, callback, data);
+
+void
+deinit()
+CODE:
+ gaim_perl_timeout_clear();
+
+
+MODULE = Gaim PACKAGE = Gaim PREFIX = gaim_
+PROTOTYPES: ENABLE
+
+Gaim::Core
+gaim_get_core()
diff --git a/libpurple/plugins/perl/common/ImgStore.xs b/libpurple/plugins/perl/common/ImgStore.xs
new file mode 100644
index 0000000000..17fba026a7
--- /dev/null
+++ b/libpurple/plugins/perl/common/ImgStore.xs
@@ -0,0 +1,35 @@
+#include "module.h"
+
+MODULE = Gaim::ImgStore PACKAGE = Gaim::ImgStore PREFIX = gaim_imgstore_
+PROTOTYPES: ENABLE
+
+int
+gaim_imgstore_add(data, size, filename)
+ void *data
+ size_t size
+ const char *filename
+
+Gaim::StoredImage
+gaim_imgstore_get(id)
+ int id
+
+gpointer
+gaim_imgstore_get_data(i)
+ Gaim::StoredImage i
+
+const char *
+gaim_imgstore_get_filename(i)
+ Gaim::StoredImage i
+
+size_t
+gaim_imgstore_get_size(i)
+ Gaim::StoredImage i
+
+void
+gaim_imgstore_ref(id)
+ int id
+
+void
+gaim_imgstore_unref(id)
+ int id
+
diff --git a/libpurple/plugins/perl/common/Log.xs b/libpurple/plugins/perl/common/Log.xs
new file mode 100644
index 0000000000..decc4b085f
--- /dev/null
+++ b/libpurple/plugins/perl/common/Log.xs
@@ -0,0 +1,94 @@
+#include "module.h"
+
+MODULE = Gaim::Log PACKAGE = Gaim::Log PREFIX = gaim_log_
+PROTOTYPES: ENABLE
+
+int
+gaim_log_common_sizer(log)
+ Gaim::Log log
+
+void
+gaim_log_common_writer(log, ext)
+ Gaim::Log log
+ const char *ext
+
+gint
+gaim_log_compare(y, z)
+ gconstpointer y
+ gconstpointer z
+
+void
+gaim_log_free(log)
+ Gaim::Log log
+
+gchar_own *
+gaim_log_get_log_dir(type, name, account)
+ Gaim::LogType type
+ const char *name
+ Gaim::Account account
+
+void
+gaim_log_get_log_sets()
+PREINIT:
+ GHashTable *l;
+PPCODE:
+ l = gaim_log_get_log_sets();
+ XPUSHs(sv_2mortal(gaim_perl_bless_object(l, "GHashTable")));
+
+void
+gaim_log_get_logs(type, name, account)
+ Gaim::LogType type
+ const char *name
+ Gaim::Account account
+PREINIT:
+ GList *l;
+PPCODE:
+ for (l = gaim_log_get_logs(type, name, account); l != NULL; l = l->next) {
+ XPUSHs(sv_2mortal(gaim_perl_bless_object(l->data, "Gaim::ListEntry")));
+ }
+
+int
+gaim_log_get_size(log)
+ Gaim::Log log
+
+void
+gaim_log_get_system_logs(account)
+ Gaim::Account account
+PREINIT:
+ GList *l;
+PPCODE:
+ for (l = gaim_log_get_system_logs(account); l != NULL; l = l->next) {
+ XPUSHs(sv_2mortal(gaim_perl_bless_object(l->data, "Gaim::ListEntry")));
+ }
+
+int
+gaim_log_get_total_size(type, name, account)
+ Gaim::LogType type
+ const char *name
+ Gaim::Account account
+
+void
+gaim_log_init()
+
+void
+gaim_log_logger_free(logger)
+ Gaim::Log::Logger logger
+
+void
+gaim_log_logger_get_options()
+PREINIT:
+ GList *l;
+PPCODE:
+ for (l = gaim_log_logger_get_options(); l != NULL; l = l->next) {
+ XPUSHs(sv_2mortal(gaim_perl_bless_object(l->data, "Gaim::ListEntry")));
+ }
+
+gchar_own *
+gaim_log_read(log, flags)
+ Gaim::Log log
+ Gaim::Log::ReadFlags flags
+
+gint
+gaim_log_set_compare(y, z)
+ gconstpointer y
+ gconstpointer z
diff --git a/libpurple/plugins/perl/common/MANIFEST b/libpurple/plugins/perl/common/MANIFEST
new file mode 100644
index 0000000000..2ed457d68b
--- /dev/null
+++ b/libpurple/plugins/perl/common/MANIFEST
@@ -0,0 +1,37 @@
+Account.xs
+AccountOpts.xs
+BuddyIcon.xs
+BuddyList.xs
+Cipher.xs
+Cmds.xs
+Connection.xs
+Conversation.xs
+Debug.xs
+FT.xs
+Gaim.pm
+Gaim.xs
+ImgStore.xs
+Log.xs
+Makefile.PL
+Network.xs
+Notify.xs
+Plugin.xs
+PluginPref.xs
+Pounce.xs
+Prefs.xs
+Privacy.xs
+Proxy.xs
+Prpl.xs
+Request.xs
+Roomlist.xs
+SSLConn.xs
+SavedStatuses.xs
+Server.xs
+Signal.xs
+Sound.xs
+Status.xs
+Stringref.xs
+Util.xs
+XMLNode.xs
+MANIFEST
+typemap
diff --git a/libpurple/plugins/perl/common/Makefile.PL.in b/libpurple/plugins/perl/common/Makefile.PL.in
new file mode 100644
index 0000000000..908c32172b
--- /dev/null
+++ b/libpurple/plugins/perl/common/Makefile.PL.in
@@ -0,0 +1,39 @@
+use 5.006;
+use ExtUtils::MakeMaker;
+# See lib/ExtUtils/MakeMaker.pm for details of how to influence
+# the contents of the Makefile that is written.
+WriteMakefile(
+ 'NAME' => 'Gaim',
+ 'VERSION_FROM' => '@srcdir@/Gaim.pm', # finds $VERSION
+ 'PREREQ_PM' => {}, # e.g., Module::Name => 1.1
+ ($] >= 5.005 ? ## Add these new keywords supported since 5.005
+ (ABSTRACT_FROM => '@srcdir@/Gaim.pm', # retrieve abstract from module
+ AUTHOR => 'Gaim <http://gaim.sourceforge.net/>') : ()),
+ 'LIBS' => [''], # e.g., '-lm'
+ 'DEFINE' => '@DEBUG_CFLAGS@', # e.g., '-DHAVE_SOMETHING'
+ 'INC' => '-I. -I@srcdir@ -I@top_srcdir@ -I@top_srcdir@/libgaim @GLIB_CFLAGS@', # e.g., '-I. -I/usr/include/other'
+ 'OBJECT' => '$(O_FILES)', # link all the C files too
+);
+
+if (eval {require ExtUtils::Constant; 1}) {
+ foreach (qw(GAIM_DEBUG_ALL GAIM_DEBUG_MISC GAIM_DEBUG_INFO
+ GAIM_DEBUG_WARNING GAIM_DEBUG_ERROR GAIM_DEBUG_FATAL)) {
+ push @names, {name => $_, type => "IV", macro => 1};
+ }
+
+ ExtUtils::Constant::WriteConstants(
+ NAME => 'Gaim::DebugLevel',
+ NAMES => \@names,
+ C_FILE => 'const-c.inc',
+ XS_FILE => 'const-xs.inc'
+ );
+}
+else {
+ use File::Copy;
+ use File::Spec;
+
+ foreach my $file ('const-c.inc', 'const-xs.inc') {
+ my $fallback = File::Spec->catfile('fallback', $file);
+ copy ($fallback, $file) or die "Can't copy $fallback to $file: $!";
+ }
+}
diff --git a/libpurple/plugins/perl/common/Makefile.mingw b/libpurple/plugins/perl/common/Makefile.mingw
new file mode 100644
index 0000000000..d884bd7073
--- /dev/null
+++ b/libpurple/plugins/perl/common/Makefile.mingw
@@ -0,0 +1,117 @@
+#
+# Makefile.mingw
+#
+# Description: Makefile for Gaim perl module.
+#
+
+GAIM_TOP := ../../../..
+include $(GAIM_TOP)/libgaim/win32/global.mak
+
+TARGET = Gaim
+AUTOSPLIT = lib/auto/Gaim/autosplit.ix
+EXTUTILS := C:/perl/lib/ExtUtils
+PERL_PLUGIN_TOP := ..
+
+CFLAGS += -Wno-comment
+
+##
+## INCLUDE PATHS
+##
+INCLUDE_PATHS = -I. \
+ -I$(GAIM_TOP) \
+ -I$(GAIM_LIB_TOP) \
+ -I$(GTK_TOP)/include \
+ -I$(GTK_TOP)/include/glib-2.0 \
+ -I$(GTK_TOP)/lib/glib-2.0/include \
+ -I$(PERL_LIB_TOP)/CORE
+
+LIB_PATHS = -L$(PERL_LIB_TOP) \
+ -L$(PERL_PLUGIN_TOP) \
+ -L$(GAIM_LIB_TOP) \
+ -L$(GTK_TOP)/lib
+
+##
+## SOURCES, OBJECTS
+##
+XS_FILES = Account.xs \
+ AccountOpts.xs \
+ BuddyIcon.xs \
+ BuddyList.xs \
+ Cipher.xs \
+ Cmds.xs \
+ Connection.xs \
+ Conversation.xs \
+ Core.xs \
+ Debug.xs \
+ FT.xs \
+ Gaim.xs \
+ ImgStore.xs \
+ Log.xs \
+ Network.xs \
+ Notify.xs \
+ Plugin.xs \
+ PluginPref.xs \
+ Pounce.xs \
+ Prefs.xs \
+ Privacy.xs \
+ Proxy.xs \
+ Prpl.xs \
+ Request.xs \
+ Roomlist.xs \
+ SSLConn.xs \
+ SavedStatuses.xs \
+ Signal.xs \
+ Server.xs \
+ Sound.xs \
+ Status.xs \
+ Stringref.xs \
+ Util.xs \
+ XMLNode.xs \
+
+FALLBACKS = const-c.inc const-xs.inc
+C_FILES = $(XS_FILES:%.xs=%.c)
+OBJECTS = $(C_FILES:%.c=%.o)
+
+##
+## LIBRARIES
+##
+LIBS = -lperl58 \
+ -lperl \
+ -lgaim \
+ -lglib-2.0
+
+include $(GAIM_COMMON_RULES)
+
+%.inc:
+ cp fallback/$@ ./
+
+##
+## TARGETS
+##
+.PHONY: all install clean
+
+all: $(TARGET).dll $(AUTOSPLIT)
+
+install: all
+ rm -rf $(GAIM_INSTALL_PERLMOD_DIR)
+ cp -R lib $(GAIM_INSTALL_PERLMOD_DIR)
+ cp $(TARGET).dll $(GAIM_INSTALL_PERLMOD_DIR)
+
+$(C_FILES): $(GAIM_CONFIG_H)
+
+$(AUTOSPLIT):
+ mkdir -p ./lib/auto
+ cp Gaim.pm ./lib
+ $(PERL) -MAutoSplit -e 'autosplit("lib/Gaim.pm")'
+
+$(TARGET).dll: $(GAIM_LIBGAIM_DLL).a $(GAIM_LIBGAIM_PERL_DLL).a $(FALLBACKS) $(OBJECTS)
+ $(CC) -shared $(OBJECTS) $(LIB_PATHS) $(LIBS) -o $(TARGET).dll
+
+##
+## CLEAN
+##
+clean:
+ rm -rf $(TARGET).dll $(FALLBACKS) lib
+ rm -f *.o $(C_FILES)
+
+include $(GAIM_COMMON_TARGETS)
diff --git a/libpurple/plugins/perl/common/Network.xs b/libpurple/plugins/perl/common/Network.xs
new file mode 100644
index 0000000000..c4cdc3daa8
--- /dev/null
+++ b/libpurple/plugins/perl/common/Network.xs
@@ -0,0 +1,45 @@
+#include "module.h"
+
+MODULE = Gaim::Network PACKAGE = Gaim::Network PREFIX = gaim_network_
+PROTOTYPES: ENABLE
+
+const char *
+gaim_network_get_local_system_ip(fd)
+ int fd
+
+const char *
+gaim_network_get_my_ip(fd)
+ int fd
+
+unsigned short
+gaim_network_get_port_from_fd(fd)
+ int fd
+
+const char *
+gaim_network_get_public_ip()
+
+void
+gaim_network_init()
+
+const unsigned char *
+gaim_network_ip_atoi(ip)
+ const char *ip
+
+Gaim::NetworkListenData
+gaim_network_listen(port, socket_type, cb, cb_data)
+ unsigned short port
+ int socket_type
+ Gaim::NetworkListenCallback cb
+ gpointer cb_data
+
+Gaim::NetworkListenData
+gaim_network_listen_range(start, end, socket_type, cb, cb_data)
+ unsigned short start
+ unsigned short end
+ int socket_type
+ Gaim::NetworkListenCallback cb
+ gpointer cb_data
+
+void
+gaim_network_set_public_ip(ip)
+ const char *ip
diff --git a/libpurple/plugins/perl/common/Notify.xs b/libpurple/plugins/perl/common/Notify.xs
new file mode 100644
index 0000000000..967ee30e84
--- /dev/null
+++ b/libpurple/plugins/perl/common/Notify.xs
@@ -0,0 +1,138 @@
+#include "module.h"
+
+MODULE = Gaim::Notify PACKAGE = Gaim::Notify PREFIX = gaim_notify_
+PROTOTYPES: ENABLE
+
+void
+gaim_notify_close(type, ui_handle)
+ Gaim::NotifyType type
+ void * ui_handle
+
+void
+gaim_notify_close_with_handle(handle)
+ void * handle
+
+void *
+gaim_notify_email(handle, subject, from, to, url, cb, user_data)
+ void * handle
+ const char *subject
+ const char *from
+ const char *to
+ const char *url
+ Gaim::NotifyCloseCallback cb
+ gpointer user_data
+
+void *
+gaim_notify_emails(handle, count, detailed, subjects, froms, tos, urls, cb, user_data)
+ void * handle
+ size_t count
+ gboolean detailed
+ const char **subjects
+ const char **froms
+ const char **tos
+ const char **urls
+ Gaim::NotifyCloseCallback cb
+ gpointer user_data
+
+void *
+gaim_notify_formatted(handle, title, primary, secondary, text, cb, user_data)
+ void * handle
+ const char *title
+ const char *primary
+ const char *secondary
+ const char *text
+ Gaim::NotifyCloseCallback cb
+ gpointer user_data
+
+void *
+gaim_notify_userinfo(gc, who, user_info, cb, user_data)
+ Gaim::Connection gc
+ const char *who
+ Gaim::NotifyUserInfo user_info
+ Gaim::NotifyCloseCallback cb
+ gpointer user_data
+
+Gaim::NotifyUserInfo
+gaim_notify_user_info_new()
+
+void
+gaim_notify_user_info_destroy(user_info)
+ Gaim::NotifyUserInfo user_info
+
+void
+gaim_notify_user_info_get_entries(user_info)
+ Gaim::NotifyUserInfo user_info
+PREINIT:
+ const GList *l;
+PPCODE:
+ l = gaim_notify_user_info_get_entries(user_info);
+ for (; l != NULL; l = l->next) {
+ XPUSHs(sv_2mortal(gaim_perl_bless_object(l->data, "Gaim::NotifyUserInfoEntry")));
+ }
+
+gchar_own *
+gaim_notify_user_info_get_text_with_newline(user_info, newline)
+ Gaim::NotifyUserInfo user_info
+ const char *newline
+
+void gaim_notify_user_info_add_pair(user_info, label, value)
+ Gaim::NotifyUserInfo user_info
+ const char *label
+ const char *value
+
+void gaim_notify_user_info_prepend_pair(user_info, label, value)
+ Gaim::NotifyUserInfo user_info
+ const char *label
+ const char *value
+
+void gaim_notify_user_info_add_section_break(user_info)
+ Gaim::NotifyUserInfo user_info
+
+void gaim_notify_user_info_add_section_header(user_info, label)
+ Gaim::NotifyUserInfo user_info
+ const char *label
+
+void gaim_notify_user_info_remove_last_item(user_info)
+ Gaim::NotifyUserInfo user_info
+
+gchar *
+gaim_notify_user_info_entry_get_label(user_info_entry)
+ Gaim::NotifyUserInfoEntry user_info_entry
+
+gchar *
+gaim_notify_user_info_entry_get_value(user_info_entry)
+ Gaim::NotifyUserInfoEntry user_info_entry
+
+Gaim::NotifyUiOps
+gaim_notify_get_ui_ops()
+
+
+void *
+gaim_notify_message(handle, type, title, primary, secondary, cb, user_data)
+ void * handle
+ Gaim::NotifyMsgType type
+ const char *title
+ const char *primary
+ const char *secondary
+ Gaim::NotifyCloseCallback cb
+ gpointer user_data
+
+void *
+gaim_notify_searchresults(gc, title, primary, secondary, results, cb, user_data)
+ Gaim::Connection gc
+ const char *title
+ const char *primary
+ const char *secondary
+ Gaim::NotifySearchResults results
+ Gaim::NotifyCloseCallback cb
+ gpointer user_data
+
+void
+gaim_notify_set_ui_ops(ops)
+ Gaim::NotifyUiOps ops
+
+void *
+gaim_notify_uri(handle, uri)
+ void * handle
+ const char *uri
+
diff --git a/libpurple/plugins/perl/common/Plugin.xs b/libpurple/plugins/perl/common/Plugin.xs
new file mode 100644
index 0000000000..35affa902d
--- /dev/null
+++ b/libpurple/plugins/perl/common/Plugin.xs
@@ -0,0 +1,156 @@
+#include "module.h"
+
+MODULE = Gaim::Plugin PACKAGE = Gaim::Plugin PREFIX = gaim_plugin_
+PROTOTYPES: ENABLE
+
+Gaim::Plugin
+gaim_plugin_new(native, path)
+ gboolean native
+ const char *path
+
+Gaim::Plugin
+gaim_plugin_probe(filename)
+ const char *filename
+
+gboolean
+gaim_plugin_register(plugin)
+ Gaim::Plugin plugin
+
+gboolean
+gaim_plugin_load(plugin)
+ Gaim::Plugin plugin
+
+gboolean
+gaim_plugin_unload(plugin)
+ Gaim::Plugin plugin
+
+gboolean
+gaim_plugin_reload(plugin)
+ Gaim::Plugin plugin
+
+void
+gaim_plugin_destroy(plugin)
+ Gaim::Plugin plugin
+
+gboolean
+gaim_plugin_is_loaded(plugin)
+ Gaim::Plugin plugin
+
+gboolean
+gaim_plugin_is_unloadable(plugin)
+ Gaim::Plugin plugin
+
+const gchar *
+gaim_plugin_get_id(plugin)
+ Gaim::Plugin plugin
+
+const gchar *
+gaim_plugin_get_name(plugin)
+ Gaim::Plugin plugin
+
+const gchar *
+gaim_plugin_get_version(plugin)
+ Gaim::Plugin plugin
+
+const gchar *
+gaim_plugin_get_summary(plugin)
+ Gaim::Plugin plugin
+
+const gchar *
+gaim_plugin_get_description(plugin)
+ Gaim::Plugin plugin
+
+const gchar *
+gaim_plugin_get_author(plugin)
+ Gaim::Plugin plugin
+
+const gchar *
+gaim_plugin_get_homepage(plugin)
+ Gaim::Plugin plugin
+
+MODULE = Gaim::Plugin PACKAGE = Gaim::Plugin::IPC PREFIX = gaim_plugin_ipc_
+
+void
+gaim_plugin_ipc_unregister(plugin, command)
+ Gaim::Plugin plugin
+ const char *command
+
+void
+gaim_plugin_ipc_unregister_all(plugin)
+ Gaim::Plugin plugin
+
+MODULE = Gaim::Plugin PACKAGE = Gaim::Plugins PREFIX = gaim_plugins_
+PROTOTYPES: ENABLE
+
+void
+gaim_plugins_add_search_path(path)
+ const char *path
+
+void
+gaim_plugins_unload_all()
+
+void
+gaim_plugins_destroy_all()
+
+void
+gaim_plugins_load_saved(key)
+ const char *key
+
+void
+gaim_plugins_probe(ext)
+ const char *ext
+
+gboolean
+gaim_plugins_enabled()
+
+Gaim::Plugin
+gaim_plugins_find_with_name(name)
+ const char *name
+
+Gaim::Plugin
+gaim_plugins_find_with_filename(filename)
+ const char *filename
+
+Gaim::Plugin
+gaim_plugins_find_with_basename(basename)
+ const char *basename
+
+Gaim::Plugin
+gaim_plugins_find_with_id(id)
+ const char *id
+
+void
+gaim_plugins_get_loaded()
+PREINIT:
+ GList *l;
+PPCODE:
+ for (l = gaim_plugins_get_loaded(); l != NULL; l = l->next) {
+ XPUSHs(sv_2mortal(gaim_perl_bless_object(l->data, "Gaim::Plugin")));
+ }
+
+void
+gaim_plugins_get_protocols()
+PREINIT:
+ GList *l;
+PPCODE:
+ for (l = gaim_plugins_get_protocols(); l != NULL; l = l->next) {
+ XPUSHs(sv_2mortal(gaim_perl_bless_object(l->data, "Gaim::Plugin")));
+ }
+
+void
+gaim_plugins_get_all()
+PREINIT:
+ GList *l;
+PPCODE:
+ for (l = gaim_plugins_get_all(); l != NULL; l = l->next) {
+ XPUSHs(sv_2mortal(gaim_perl_bless_object(l->data, "Gaim::Plugin")));
+ }
+
+Gaim::Handle
+gaim_plugins_get_handle()
+
+void
+gaim_plugins_init()
+
+void
+gaim_plugins_uninit()
diff --git a/libpurple/plugins/perl/common/PluginPref.xs b/libpurple/plugins/perl/common/PluginPref.xs
new file mode 100644
index 0000000000..ac42b4a69d
--- /dev/null
+++ b/libpurple/plugins/perl/common/PluginPref.xs
@@ -0,0 +1,144 @@
+#include "module.h"
+
+MODULE = Gaim::PluginPref PACKAGE = Gaim::PluginPref::Frame PREFIX = gaim_plugin_pref_frame_
+PROTOTYPES: ENABLE
+
+void
+gaim_plugin_pref_frame_add(frame, pref)
+ Gaim::PluginPref::Frame frame
+ Gaim::PluginPref pref
+
+void
+gaim_plugin_pref_frame_destroy(frame)
+ Gaim::PluginPref::Frame frame
+
+void
+gaim_plugin_pref_frame_get_prefs(frame)
+ Gaim::PluginPref::Frame frame
+PREINIT:
+ GList *l;
+PPCODE:
+ for (l = gaim_plugin_pref_frame_get_prefs(frame); l != NULL; l = l->next) {
+ XPUSHs(sv_2mortal(gaim_perl_bless_object(l->data, "Gaim::PluginPref")));
+ }
+
+Gaim::PluginPref::Frame
+gaim_plugin_pref_frame_new(class)
+ C_ARGS: /* void */
+
+MODULE = Gaim::PluginPref PACKAGE = Gaim::PluginPref PREFIX = gaim_plugin_pref_
+PROTOTYPES: ENABLE
+
+void
+gaim_plugin_pref_add_choice(pref, label, choice)
+ Gaim::PluginPref pref
+ const char *label
+# Do the appropriate conversion based on the perl type specified.
+# Currently only Strings and Ints will work.
+ gpointer choice = (SvPOKp($arg) ? SvPV($arg, PL_na) : (SvIOKp($arg) ? GINT_TO_POINTER(SvIV($arg)) : NULL));
+
+void
+gaim_plugin_pref_destroy(pref)
+ Gaim::PluginPref pref
+
+
+void
+gaim_plugin_pref_get_bounds(pref, min, max)
+ Gaim::PluginPref pref
+ int *min
+ int *max
+
+void
+gaim_plugin_pref_get_choices(pref)
+ Gaim::PluginPref pref
+PREINIT:
+ GList *l;
+PPCODE:
+ for (l = gaim_plugin_pref_get_choices(pref); l != NULL; l = l->next) {
+ XPUSHs(sv_2mortal(gaim_perl_bless_object(l->data, "Gaim::ListItem")));
+ }
+
+const char *
+gaim_plugin_pref_get_label(pref)
+ Gaim::PluginPref pref
+
+gboolean
+gaim_plugin_pref_get_masked(pref)
+ Gaim::PluginPref pref
+
+unsigned int
+gaim_plugin_pref_get_max_length(pref)
+ Gaim::PluginPref pref
+
+const char *
+gaim_plugin_pref_get_name(pref)
+ Gaim::PluginPref pref
+
+Gaim::PluginPrefType
+gaim_plugin_pref_get_type(pref)
+ Gaim::PluginPref pref
+
+Gaim::PluginPref
+gaim_plugin_pref_new(class)
+ C_ARGS: /* void */
+
+Gaim::PluginPref
+gaim_plugin_pref_new_with_label(class, label)
+ const char *label
+ C_ARGS:
+ label
+
+Gaim::PluginPref
+gaim_plugin_pref_new_with_name(class, name)
+ const char *name
+ C_ARGS:
+ name
+
+Gaim::PluginPref
+gaim_plugin_pref_new_with_name_and_label(class, name, label)
+ const char *name
+ const char *label
+ C_ARGS:
+ name, label
+
+void
+gaim_plugin_pref_set_bounds(pref, min, max)
+ Gaim::PluginPref pref
+ int min
+ int max
+
+void
+gaim_plugin_pref_set_label(pref, label)
+ Gaim::PluginPref pref
+ const char *label
+
+void
+gaim_plugin_pref_set_masked(pref, mask)
+ Gaim::PluginPref pref
+ gboolean mask
+
+void
+gaim_plugin_pref_set_max_length(pref, max_length)
+ Gaim::PluginPref pref
+ unsigned int max_length
+
+void
+gaim_plugin_pref_set_name(pref, name)
+ Gaim::PluginPref pref
+ const char *name
+
+void
+gaim_plugin_pref_set_type(pref, type)
+ Gaim::PluginPref pref
+ Gaim::PluginPrefType type
+PREINIT:
+ GaimPluginPrefType gpp_type;
+CODE:
+ gpp_type = GAIM_PLUGIN_PREF_NONE;
+
+ if (type == 1) {
+ gpp_type = GAIM_PLUGIN_PREF_CHOICE;
+ } else if (type == 2) {
+ gpp_type = GAIM_PLUGIN_PREF_INFO;
+ }
+ gaim_plugin_pref_set_type(pref, gpp_type);
diff --git a/libpurple/plugins/perl/common/Pounce.xs b/libpurple/plugins/perl/common/Pounce.xs
new file mode 100644
index 0000000000..d9ab8960ae
--- /dev/null
+++ b/libpurple/plugins/perl/common/Pounce.xs
@@ -0,0 +1,90 @@
+#include "module.h"
+
+MODULE = Gaim::Pounce PACKAGE = Gaim::Pounce PREFIX = gaim_pounce_
+PROTOTYPES: ENABLE
+
+void
+gaim_pounce_action_register(pounce, name)
+ Gaim::Pounce pounce
+ const char *name
+
+void
+gaim_pounce_destroy(pounce)
+ Gaim::Pounce pounce
+
+void
+gaim_pounce_destroy_all_by_account(account)
+ Gaim::Account account
+
+void *
+gaim_pounce_get_data(pounce)
+ Gaim::Pounce pounce
+
+Gaim::PounceEvent
+gaim_pounce_get_events(pounce)
+ Gaim::Pounce pounce
+
+const char *
+gaim_pounce_get_pouncee(pounce)
+ Gaim::Pounce pounce
+
+Gaim::Account
+gaim_pounce_get_pouncer(pounce)
+ Gaim::Pounce pounce
+
+gboolean
+gaim_pounce_get_save(pounce)
+ Gaim::Pounce pounce
+
+void
+gaim_pounce_set_data(pounce, data)
+ Gaim::Pounce pounce
+ void * data
+
+void
+gaim_pounce_set_events(pounce, events)
+ Gaim::Pounce pounce
+ Gaim::PounceEvent events
+
+void
+gaim_pounce_set_pouncee(pounce, pouncee)
+ Gaim::Pounce pounce
+ const char *pouncee
+
+void
+gaim_pounce_set_pouncer(pounce, pouncer)
+ Gaim::Pounce pounce
+ Gaim::Account pouncer
+
+void
+gaim_pounce_set_save(pounce, save)
+ Gaim::Pounce pounce
+ gboolean save
+
+MODULE = Gaim::Pounce PACKAGE = Gaim::Pounces PREFIX = gaim_pounces_
+PROTOTYPES: ENABLE
+
+void
+gaim_pounces_get_all()
+PREINIT:
+ GList *l;
+PPCODE:
+ for (l = gaim_pounces_get_all(); l != NULL; l = l->next) {
+ XPUSHs(sv_2mortal(gaim_perl_bless_object(l->data, "Gaim::Pounce")));
+ }
+
+Gaim::Handle
+gaim_pounces_get_handle()
+
+void
+gaim_pounces_init()
+
+gboolean
+gaim_pounces_load()
+
+void
+gaim_pounces_uninit()
+
+void
+gaim_pounces_unregister_handler(ui)
+ const char *ui
diff --git a/libpurple/plugins/perl/common/Prefs.xs b/libpurple/plugins/perl/common/Prefs.xs
new file mode 100644
index 0000000000..5e169b12f7
--- /dev/null
+++ b/libpurple/plugins/perl/common/Prefs.xs
@@ -0,0 +1,151 @@
+#include "module.h"
+
+MODULE = Gaim::Prefs PACKAGE = Gaim::Prefs PREFIX = gaim_prefs_
+PROTOTYPES: ENABLE
+
+void
+gaim_prefs_add_bool(name, value)
+ const char *name
+ gboolean value
+
+void
+gaim_prefs_add_int(name, value)
+ const char *name
+ int value
+
+void
+gaim_prefs_add_none(name)
+ const char *name
+
+void
+gaim_prefs_add_string(name, value)
+ const char *name
+ const char *value
+
+void
+gaim_prefs_add_string_list(name, value)
+ const char *name
+ SV *value
+PREINIT:
+ GList *t_GL;
+ int i, t_len;
+PPCODE:
+ t_GL = NULL;
+ t_len = av_len((AV *)SvRV(value));
+
+ for (i = 0; i < t_len; i++) {
+ STRLEN t_sl;
+ t_GL = g_list_append(t_GL, SvPV(*av_fetch((AV *)SvRV(value), i, 0), t_sl));
+ }
+ gaim_prefs_add_string_list(name, t_GL);
+
+void
+gaim_prefs_destroy()
+
+void
+gaim_prefs_disconnect_by_handle(handle)
+ void * handle
+
+void
+gaim_prefs_disconnect_callback(callback_id)
+ guint callback_id
+
+gboolean
+gaim_prefs_exists(name)
+ const char *name
+
+gboolean
+gaim_prefs_get_bool(name)
+ const char *name
+
+Gaim::Handle
+gaim_prefs_get_handle()
+
+int
+gaim_prefs_get_int(name)
+ const char *name
+
+const char *
+gaim_prefs_get_string(name)
+ const char *name
+
+void
+gaim_prefs_get_string_list(name)
+ const char *name
+PREINIT:
+ GList *l;
+PPCODE:
+ for (l = gaim_prefs_get_string_list(name); l != NULL; l = l->next) {
+ XPUSHs(sv_2mortal(gaim_perl_bless_object(l->data, "Gaim::PrefValue")));
+ }
+
+Gaim::PrefType
+gaim_prefs_get_type(name)
+ const char *name
+
+void
+gaim_prefs_init()
+
+gboolean
+gaim_prefs_load()
+
+void
+gaim_prefs_remove(name)
+ const char *name
+
+void
+gaim_prefs_rename(oldname, newname)
+ const char *oldname
+ const char *newname
+
+void
+gaim_prefs_rename_boolean_toggle(oldname, newname)
+ const char *oldname
+ const char *newname
+
+void
+gaim_prefs_set_bool(name, value)
+ const char *name
+ gboolean value
+
+void
+gaim_prefs_set_generic(name, value)
+ const char *name
+ gpointer value
+
+void
+gaim_prefs_set_int(name, value)
+ const char *name
+ int value
+
+void
+gaim_prefs_set_string(name, value)
+ const char *name
+ const char *value
+
+void
+gaim_prefs_set_string_list(name, value)
+ const char *name
+ SV *value
+PREINIT:
+ GList *t_GL;
+ int i, t_len;
+PPCODE:
+ t_GL = NULL;
+ t_len = av_len((AV *)SvRV(value));
+
+ for (i = 0; i < t_len; i++) {
+ STRLEN t_sl;
+ t_GL = g_list_append(t_GL, SvPV(*av_fetch((AV *)SvRV(value), i, 0), t_sl));
+ }
+ gaim_prefs_set_string_list(name, t_GL);
+
+void
+gaim_prefs_trigger_callback(name)
+ const char *name
+
+void
+gaim_prefs_uninit()
+
+void
+gaim_prefs_update_old()
diff --git a/libpurple/plugins/perl/common/Privacy.xs b/libpurple/plugins/perl/common/Privacy.xs
new file mode 100644
index 0000000000..601fe08d4a
--- /dev/null
+++ b/libpurple/plugins/perl/common/Privacy.xs
@@ -0,0 +1,43 @@
+#include "module.h"
+
+MODULE = Gaim::Privacy PACKAGE = Gaim::Privacy PREFIX = gaim_privacy_
+PROTOTYPES: ENABLE
+
+Gaim::Privacy::UiOps
+gaim_privacy_get_ui_ops()
+
+void
+gaim_privacy_init()
+
+void
+gaim_privacy_set_ui_ops(ops)
+ Gaim::Privacy::UiOps ops
+
+gboolean
+gaim_privacy_permit_add(account, name, local_only)
+ Gaim::Account account
+ const char * name
+ gboolean local_only
+
+gboolean
+gaim_privacy_permit_remove(account, name, local_only)
+ Gaim::Account account
+ const char * name
+ gboolean local_only
+
+gboolean
+gaim_privacy_deny_add(account, name, local_only)
+ Gaim::Account account
+ const char * name
+ gboolean local_only
+
+gboolean
+gaim_privacy_deny_remove(account, name, local_only)
+ Gaim::Account account
+ const char * name
+ gboolean local_only
+
+gboolean
+gaim_privacy_check(account, who)
+ Gaim::Account account
+ const char * who
diff --git a/libpurple/plugins/perl/common/Proxy.xs b/libpurple/plugins/perl/common/Proxy.xs
new file mode 100644
index 0000000000..f21e737431
--- /dev/null
+++ b/libpurple/plugins/perl/common/Proxy.xs
@@ -0,0 +1,65 @@
+#include "module.h"
+
+MODULE = Gaim::Proxy PACKAGE = Gaim::Proxy PREFIX = gaim_proxy_
+PROTOTYPES: ENABLE
+
+Gaim::ProxyInfo
+gaim_global_proxy_get_info()
+
+Gaim::Handle
+gaim_proxy_get_handle()
+
+void
+gaim_proxy_info_destroy(info)
+ Gaim::ProxyInfo info
+
+const char *
+gaim_proxy_info_get_host(info)
+ Gaim::ProxyInfo info
+
+const char *
+gaim_proxy_info_get_password(info)
+ Gaim::ProxyInfo info
+
+int
+gaim_proxy_info_get_port(info)
+ Gaim::ProxyInfo info
+
+Gaim::ProxyType
+gaim_proxy_info_get_type(info)
+ Gaim::ProxyInfo info
+
+const char *
+gaim_proxy_info_get_username(info)
+ Gaim::ProxyInfo info
+
+Gaim::ProxyInfo
+gaim_proxy_info_new()
+
+void
+gaim_proxy_info_set_host(info, host)
+ Gaim::ProxyInfo info
+ const char *host
+
+void
+gaim_proxy_info_set_password(info, password)
+ Gaim::ProxyInfo info
+ const char *password
+
+void
+gaim_proxy_info_set_port(info, port)
+ Gaim::ProxyInfo info
+ int port
+
+void
+gaim_proxy_info_set_type(info, type)
+ Gaim::ProxyInfo info
+ Gaim::ProxyType type
+
+void
+gaim_proxy_info_set_username(info, username)
+ Gaim::ProxyInfo info
+ const char *username
+
+void
+gaim_proxy_init()
diff --git a/libpurple/plugins/perl/common/Prpl.xs b/libpurple/plugins/perl/common/Prpl.xs
new file mode 100644
index 0000000000..757d4161cb
--- /dev/null
+++ b/libpurple/plugins/perl/common/Prpl.xs
@@ -0,0 +1,54 @@
+#include "module.h"
+
+MODULE = Gaim::Prpl PACKAGE = Gaim::Find PREFIX = gaim_find_
+PROTOTYPES: ENABLE
+
+Gaim::Plugin
+gaim_find_prpl(id)
+ const char *id
+
+MODULE = Gaim::Prpl PACKAGE = Gaim::Prpl PREFIX = gaim_prpl_
+PROTOTYPES: ENABLE
+
+void
+gaim_prpl_change_account_status(account, old_status, new_status)
+ Gaim::Account account
+ Gaim::Status old_status
+ Gaim::Status new_status
+
+void
+gaim_prpl_get_statuses(account, presence)
+ Gaim::Account account
+ Gaim::Presence presence
+PREINIT:
+ GList *l;
+PPCODE:
+ for (l = gaim_prpl_get_statuses(account,presence); l != NULL; l = l->next) {
+ /* XXX Someone please test and make sure this is the right
+ * type for these things. */
+ XPUSHs(sv_2mortal(gaim_perl_bless_object(l->data, "Gaim::Status")));
+ }
+
+void
+gaim_prpl_got_account_idle(account, idle, idle_time)
+ Gaim::Account account
+ gboolean idle
+ time_t idle_time
+
+void
+gaim_prpl_got_account_login_time(account, login_time)
+ Gaim::Account account
+ time_t login_time
+
+void
+gaim_prpl_got_user_idle(account, name, idle, idle_time)
+ Gaim::Account account
+ const char *name
+ gboolean idle
+ time_t idle_time
+
+void
+gaim_prpl_got_user_login_time(account, name, login_time)
+ Gaim::Account account
+ const char *name
+ time_t login_time
diff --git a/libpurple/plugins/perl/common/Request.xs b/libpurple/plugins/perl/common/Request.xs
new file mode 100644
index 0000000000..ba5369a8b4
--- /dev/null
+++ b/libpurple/plugins/perl/common/Request.xs
@@ -0,0 +1,583 @@
+#include "module.h"
+
+/* This breaks on faceprint's amd64 box
+void *
+gaim_request_action_varg(handle, title, primary, secondary, default_action, user_data, action_count, actions)
+ void * handle
+ const char *title
+ const char *primary
+ const char *secondary
+ unsigned int default_action
+ void *user_data
+ size_t action_count
+ va_list actions
+ */
+
+
+typedef struct {
+ char *cancel_cb;
+ char *ok_cb;
+} GaimPerlRequestData;
+
+/********************************************************/
+/* */
+/* Callback function that calls a perl subroutine */
+/* */
+/* The void * field data is being used as a way to hide */
+/* the perl sub's name in a GaimPerlRequestData */
+/* */
+/********************************************************/
+static void
+gaim_perl_request_ok_cb(void * data, GaimRequestFields *fields)
+{
+ GaimPerlRequestData *gpr = (GaimPerlRequestData *)data;
+
+ dSP;
+ ENTER;
+ SAVETMPS;
+ PUSHMARK(sp);
+
+ XPUSHs(gaim_perl_bless_object(fields, "Gaim::Request::Fields"));
+ PUTBACK;
+
+ call_pv(gpr->ok_cb, G_EVAL | G_SCALAR);
+ SPAGAIN;
+
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+
+ g_free(gpr->ok_cb);
+ g_free(gpr->cancel_cb);
+ g_free(gpr);
+}
+
+static void
+gaim_perl_request_cancel_cb(void * data, GaimRequestFields *fields)
+{
+
+ GaimPerlRequestData *gpr = (GaimPerlRequestData *)data;
+
+ dSP;
+ ENTER;
+ SAVETMPS;
+ PUSHMARK(sp);
+
+ XPUSHs(gaim_perl_bless_object(fields, "Gaim::Request::Fields"));
+ PUTBACK;
+ call_pv(gpr->cancel_cb, G_EVAL | G_SCALAR);
+ SPAGAIN;
+
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+
+ g_free(gpr->ok_cb);
+ g_free(gpr->cancel_cb);
+ g_free(gpr);
+}
+
+MODULE = Gaim::Request PACKAGE = Gaim::Request PREFIX = gaim_request_
+PROTOTYPES: ENABLE
+
+void *
+gaim_request_input(handle, title, primary, secondary, default_value, multiline, masked, hint, ok_text, ok_cb, cancel_text, cancel_cb)
+ Gaim::Plugin handle
+ const char * title
+ const char * primary
+ const char * secondary
+ const char * default_value
+ gboolean multiline
+ gboolean masked
+ gchar * hint
+ const char * ok_text
+ SV * ok_cb
+ const char * cancel_text
+ SV * cancel_cb
+CODE:
+ GaimPerlRequestData *gpr;
+ STRLEN len;
+ char *basename;
+
+ basename = g_path_get_basename(handle->path);
+ gaim_perl_normalize_script_name(basename);
+ gpr = g_new(GaimPerlRequestData, 1);
+ gpr->ok_cb = g_strdup_printf("Gaim::Script::%s::%s", basename, SvPV(ok_cb, len));
+ gpr->cancel_cb = g_strdup_printf("Gaim::Script::%s::%s", basename, SvPV(cancel_cb, len));
+ g_free(basename);
+
+ RETVAL = gaim_request_input(handle, title, primary, secondary, default_value, multiline, masked, hint, ok_text, G_CALLBACK(gaim_perl_request_ok_cb), cancel_text, G_CALLBACK(gaim_perl_request_cancel_cb), gpr);
+OUTPUT:
+ RETVAL
+
+void *
+gaim_request_file(handle, title, filename, savedialog, ok_cb, cancel_cb)
+ Gaim::Plugin handle
+ const char * title
+ const char * filename
+ gboolean savedialog
+ SV * ok_cb
+ SV * cancel_cb
+CODE:
+ GaimPerlRequestData *gpr;
+ STRLEN len;
+ char *basename;
+
+ basename = g_path_get_basename(handle->path);
+ gaim_perl_normalize_script_name(basename);
+ gpr = g_new(GaimPerlRequestData, 1);
+ gpr->ok_cb = g_strdup_printf("Gaim::Script::%s::%s", basename, SvPV(ok_cb, len));
+ gpr->cancel_cb = g_strdup_printf("Gaim::Script::%s::%s", basename, SvPV(cancel_cb, len));
+ g_free(basename);
+
+ RETVAL = gaim_request_file(handle, title, filename, savedialog, G_CALLBACK(gaim_perl_request_ok_cb), G_CALLBACK(gaim_perl_request_cancel_cb), gpr);
+OUTPUT:
+ RETVAL
+
+void *
+gaim_request_fields(handle, title, primary, secondary, fields, ok_text, ok_cb, cancel_text, cancel_cb)
+ Gaim::Plugin handle
+ const char * title
+ const char * primary
+ const char * secondary
+ Gaim::Request::Fields fields
+ const char * ok_text
+ SV * ok_cb
+ const char * cancel_text
+ SV * cancel_cb
+CODE:
+ GaimPerlRequestData *gpr;
+ STRLEN len;
+ char *basename;
+
+ basename = g_path_get_basename(handle->path);
+ gaim_perl_normalize_script_name(basename);
+ gpr = g_new(GaimPerlRequestData, 1);
+ gpr->ok_cb = g_strdup_printf("Gaim::Script::%s::%s", basename, SvPV(ok_cb, len));
+ gpr->cancel_cb = g_strdup_printf("Gaim::Script::%s::%s", basename, SvPV(cancel_cb, len));
+ g_free(basename);
+
+ RETVAL = gaim_request_fields(handle, title, primary, secondary, fields, ok_text, G_CALLBACK(gaim_perl_request_ok_cb), cancel_text, G_CALLBACK(gaim_perl_request_cancel_cb), gpr);
+OUTPUT:
+ RETVAL
+
+void
+gaim_request_close(type, uihandle)
+ Gaim::RequestType type
+ void * uihandle
+
+void
+gaim_request_close_with_handle(handle)
+ void * handle
+
+MODULE = Gaim::Request PACKAGE = Gaim::Request::Field PREFIX = gaim_request_field_
+PROTOTYPES: ENABLE
+
+Gaim::Account
+gaim_request_field_account_get_default_value(field)
+ Gaim::Request::Field field
+
+IV
+gaim_request_field_account_get_filter(field)
+ Gaim::Request::Field field
+CODE:
+ RETVAL = PTR2IV(gaim_request_field_account_get_filter(field));
+OUTPUT:
+ RETVAL
+
+gboolean
+gaim_request_field_account_get_show_all(field)
+ Gaim::Request::Field field
+
+Gaim::Account
+gaim_request_field_account_get_value(field)
+ Gaim::Request::Field field
+
+Gaim::Request::Field
+gaim_request_field_account_new(id, text, account = NULL)
+ const char *id
+ const char *text
+ Gaim::Account account
+
+void
+gaim_request_field_account_set_default_value(field, default_value)
+ Gaim::Request::Field field
+ Gaim::Account default_value
+
+void
+gaim_request_field_account_set_show_all(field, show_all)
+ Gaim::Request::Field field
+ gboolean show_all
+
+void
+gaim_request_field_account_set_value(field, value)
+ Gaim::Request::Field field
+ Gaim::Account value
+
+gboolean
+gaim_request_field_bool_get_default_value(field)
+ Gaim::Request::Field field
+
+gboolean
+gaim_request_field_bool_get_value(field)
+ Gaim::Request::Field field
+
+Gaim::Request::Field
+gaim_request_field_bool_new(id, text, default_value = TRUE)
+ const char *id
+ const char *text
+ gboolean default_value
+
+void
+gaim_request_field_bool_set_default_value(field, default_value)
+ Gaim::Request::Field field
+ gboolean default_value
+
+void
+gaim_request_field_bool_set_value(field, value)
+ Gaim::Request::Field field
+ gboolean value
+
+void
+gaim_request_field_choice_add(field, label)
+ Gaim::Request::Field field
+ const char *label
+
+int
+gaim_request_field_choice_get_default_value(field)
+ Gaim::Request::Field field
+
+void
+gaim_request_field_choice_get_labels(field)
+ Gaim::Request::Field field
+PREINIT:
+ GList *l;
+PPCODE:
+ for (l = gaim_request_field_choice_get_labels(field); l != NULL; l = l->next) {
+ XPUSHs(sv_2mortal(newSVpv(l->data, 0)));
+ }
+
+int
+gaim_request_field_choice_get_value(field)
+ Gaim::Request::Field field
+
+Gaim::Request::Field
+gaim_request_field_choice_new(id, text, default_value = 0)
+ const char *id
+ const char *text
+ int default_value
+
+void
+gaim_request_field_choice_set_default_value(field, default_value)
+ Gaim::Request::Field field
+ int default_value
+
+void
+gaim_request_field_choice_set_value(field, value)
+ Gaim::Request::Field field
+ int value
+
+void
+gaim_request_field_destroy(field)
+ Gaim::Request::Field field
+
+const char *
+gaim_request_field_get_id(field)
+ Gaim::Request::Field field
+
+const char *
+gaim_request_field_get_label(field)
+ Gaim::Request::Field field
+
+Gaim::RequestFieldType
+gaim_request_field_get_type(field)
+ Gaim::Request::Field field
+
+const char *
+gaim_request_field_get_type_hint(field)
+ Gaim::Request::Field field
+
+int
+gaim_request_field_int_get_default_value(field)
+ Gaim::Request::Field field
+
+int
+gaim_request_field_int_get_value(field)
+ Gaim::Request::Field field
+
+Gaim::Request::Field
+gaim_request_field_int_new(id, text, default_value = 0)
+ const char *id
+ const char *text
+ int default_value
+
+void
+gaim_request_field_int_set_default_value(field, default_value)
+ Gaim::Request::Field field
+ int default_value
+
+void
+gaim_request_field_int_set_value(field, value)
+ Gaim::Request::Field field
+ int value
+
+gboolean
+gaim_request_field_is_required(field)
+ Gaim::Request::Field field
+
+gboolean
+gaim_request_field_is_visible(field)
+ Gaim::Request::Field field
+
+Gaim::Request::Field
+gaim_request_field_label_new(id, text)
+ const char *id
+ const char *text
+
+void
+gaim_request_field_list_add(field, item, data)
+ Gaim::Request::Field field
+ const char *item
+ void * data
+
+void
+gaim_request_field_list_add_selected(field, item)
+ Gaim::Request::Field field
+ const char *item
+
+void
+gaim_request_field_list_clear_selected(field)
+ Gaim::Request::Field field
+
+void *
+gaim_request_field_list_get_data(field, text)
+ Gaim::Request::Field field
+ const char *text
+
+void
+gaim_request_field_list_get_items(field)
+ Gaim::Request::Field field
+PREINIT:
+ const GList *l;
+PPCODE:
+ for (l = gaim_request_field_list_get_items(field); l != NULL; l = l->next) {
+ XPUSHs(sv_2mortal(newSVpv(l->data, 0)));
+ }
+
+gboolean
+gaim_request_field_list_get_multi_select(field)
+ Gaim::Request::Field field
+
+void
+gaim_request_field_list_get_selected(field)
+ Gaim::Request::Field field
+PREINIT:
+ const GList *l;
+PPCODE:
+ for (l = gaim_request_field_list_get_selected(field); l != NULL; l = l->next) {
+ XPUSHs(sv_2mortal(newSVpv(l->data, 0)));
+ }
+
+gboolean
+gaim_request_field_list_is_selected(field, item)
+ Gaim::Request::Field field
+ const char *item
+
+Gaim::Request::Field
+gaim_request_field_list_new(id, text)
+ const char *id
+ const char *text
+
+void
+gaim_request_field_list_set_multi_select(field, multi_select)
+ Gaim::Request::Field field
+ gboolean multi_select
+
+Gaim::Request::Field
+gaim_request_field_new(id, text, type)
+ const char *id
+ const char *text
+ Gaim::RequestFieldType type
+
+void
+gaim_request_field_set_label(field, label)
+ Gaim::Request::Field field
+ const char *label
+
+void
+gaim_request_field_set_required(field, required)
+ Gaim::Request::Field field
+ gboolean required
+
+void
+gaim_request_field_set_type_hint(field, type_hint)
+ Gaim::Request::Field field
+ const char *type_hint
+
+void
+gaim_request_field_set_visible(field, visible)
+ Gaim::Request::Field field
+ gboolean visible
+
+const char *
+gaim_request_field_string_get_default_value(field)
+ Gaim::Request::Field field
+
+const char *
+gaim_request_field_string_get_value(field)
+ Gaim::Request::Field field
+
+gboolean
+gaim_request_field_string_is_editable(field)
+ Gaim::Request::Field field
+
+gboolean
+gaim_request_field_string_is_masked(field)
+ Gaim::Request::Field field
+
+gboolean
+gaim_request_field_string_is_multiline(field)
+ Gaim::Request::Field field
+
+Gaim::Request::Field
+gaim_request_field_string_new(id, text, default_value, multiline)
+ const char *id
+ const char *text
+ const char *default_value
+ gboolean multiline
+
+void
+gaim_request_field_string_set_default_value(field, default_value)
+ Gaim::Request::Field field
+ const char *default_value
+
+void
+gaim_request_field_string_set_editable(field, editable)
+ Gaim::Request::Field field
+ gboolean editable
+
+void
+gaim_request_field_string_set_masked(field, masked)
+ Gaim::Request::Field field
+ gboolean masked
+
+void
+gaim_request_field_string_set_value(field, value)
+ Gaim::Request::Field field
+ const char *value
+
+Gaim::Request::UiOps
+gaim_request_get_ui_ops()
+
+void
+gaim_request_set_ui_ops(ops)
+ Gaim::Request::UiOps ops
+
+MODULE = Gaim::Request PACKAGE = Gaim::Request::Field::Group PREFIX = gaim_request_field_group_
+PROTOTYPES: ENABLE
+
+void
+gaim_request_field_group_add_field(group, field)
+ Gaim::Request::Field::Group group
+ Gaim::Request::Field field
+
+void
+gaim_request_field_group_destroy(group)
+ Gaim::Request::Field::Group group
+
+void
+gaim_request_field_group_get_fields(group)
+ Gaim::Request::Field::Group group
+PREINIT:
+ GList *l;
+PPCODE:
+ for (l = gaim_request_field_group_get_fields(group); l != NULL; l = l->next) {
+ XPUSHs(sv_2mortal(gaim_perl_bless_object(l->data, "Gaim::Request::Field")));
+ }
+
+const char *
+gaim_request_field_group_get_title(group)
+ Gaim::Request::Field::Group group
+
+Gaim::Request::Field::Group
+gaim_request_field_group_new(title)
+ const char *title
+
+MODULE = Gaim::Request PACKAGE = Gaim::Request::Fields PREFIX = gaim_request_fields_
+PROTOTYPES: ENABLE
+
+void
+gaim_request_fields_add_group(fields, group)
+ Gaim::Request::Fields fields
+ Gaim::Request::Field::Group group
+
+gboolean
+gaim_request_fields_all_required_filled(fields)
+ Gaim::Request::Fields fields
+
+void
+gaim_request_fields_destroy(fields)
+ Gaim::Request::Fields fields
+
+gboolean
+gaim_request_fields_exists(fields, id)
+ Gaim::Request::Fields fields
+ const char *id
+
+Gaim::Account
+gaim_request_fields_get_account(fields, id)
+ Gaim::Request::Fields fields
+ const char *id
+
+gboolean
+gaim_request_fields_get_bool(fields, id)
+ Gaim::Request::Fields fields
+ const char *id
+
+int
+gaim_request_fields_get_choice(fields, id)
+ Gaim::Request::Fields fields
+ const char *id
+
+Gaim::Request::Field
+gaim_request_fields_get_field(fields, id)
+ Gaim::Request::Fields fields
+ const char *id
+
+void
+gaim_request_fields_get_groups(fields)
+ Gaim::Request::Fields fields
+PREINIT:
+ GList *l;
+PPCODE:
+ for (l = gaim_request_fields_get_groups(fields); l != NULL; l = l->next) {
+ XPUSHs(sv_2mortal(gaim_perl_bless_object(l->data, "Gaim::Request::Field::Group")));
+ }
+
+int
+gaim_request_fields_get_integer(fields, id)
+ Gaim::Request::Fields fields
+ const char *id
+
+void
+gaim_request_fields_get_required(fields)
+ Gaim::Request::Fields fields
+PREINIT:
+ const GList *l;
+PPCODE:
+ for (l = gaim_request_fields_get_required(fields); l != NULL; l = l->next) {
+ XPUSHs(sv_2mortal(gaim_perl_bless_object(l->data, "Gaim::Request::Field")));
+ }
+
+const char *
+gaim_request_fields_get_string(fields, id)
+ Gaim::Request::Fields fields
+ const char *id
+
+gboolean
+gaim_request_fields_is_field_required(fields, id)
+ Gaim::Request::Fields fields
+ const char *id
+
+Gaim::Request::Fields
+gaim_request_fields_new()
diff --git a/libpurple/plugins/perl/common/Roomlist.xs b/libpurple/plugins/perl/common/Roomlist.xs
new file mode 100644
index 0000000000..eee92aee15
--- /dev/null
+++ b/libpurple/plugins/perl/common/Roomlist.xs
@@ -0,0 +1,84 @@
+#include "module.h"
+
+MODULE = Gaim::Roomlist PACKAGE = Gaim::Roomlist PREFIX = gaim_roomlist_
+PROTOTYPES: ENABLE
+
+void
+gaim_roomlist_cancel_get_list(list)
+ Gaim::Roomlist list
+
+void
+gaim_roomlist_expand_category(list, category)
+ Gaim::Roomlist list
+ Gaim::Roomlist::Room category
+
+gboolean
+gaim_roomlist_get_in_progress(list)
+ Gaim::Roomlist list
+
+Gaim::Roomlist
+gaim_roomlist_get_list(gc)
+ Gaim::Connection gc
+
+Gaim::Roomlist::UiOps
+gaim_roomlist_get_ui_ops()
+
+
+Gaim::Roomlist
+gaim_roomlist_new(account)
+ Gaim::Account account
+
+void
+gaim_roomlist_ref(list)
+ Gaim::Roomlist list
+
+void
+gaim_roomlist_room_add(list, room)
+ Gaim::Roomlist list
+ Gaim::Roomlist::Room room
+
+void
+gaim_roomlist_room_add_field(list, room, field)
+ Gaim::Roomlist list
+ Gaim::Roomlist::Room room
+ gconstpointer field
+
+void
+gaim_roomlist_room_join(list, room)
+ Gaim::Roomlist list
+ Gaim::Roomlist::Room room
+
+void
+gaim_roomlist_set_fields(list, fields)
+ Gaim::Roomlist list
+ SV *fields
+PREINIT:
+ GList *t_GL;
+ int i, t_len;
+PPCODE:
+ t_GL = NULL;
+ t_len = av_len((AV *)SvRV(fields));
+
+ for (i = 0; i < t_len; i++) {
+ STRLEN t_sl;
+ t_GL = g_list_append(t_GL, SvPV(*av_fetch((AV *)SvRV(fields), i, 0), t_sl));
+ }
+ gaim_roomlist_set_fields(list, t_GL);
+
+void
+gaim_roomlist_set_in_progress(list, in_progress)
+ Gaim::Roomlist list
+ gboolean in_progress
+
+void
+gaim_roomlist_set_ui_ops(ops)
+ Gaim::Roomlist::UiOps ops
+
+void
+gaim_roomlist_show_with_account(account)
+ Gaim::Account account
+
+void
+gaim_roomlist_unref(list)
+ Gaim::Roomlist list
+
diff --git a/libpurple/plugins/perl/common/SSLConn.xs b/libpurple/plugins/perl/common/SSLConn.xs
new file mode 100644
index 0000000000..2b6e5ac00e
--- /dev/null
+++ b/libpurple/plugins/perl/common/SSLConn.xs
@@ -0,0 +1,61 @@
+#include "module.h"
+
+/* TODO
+
+
+Gaim::Ssl::Connection
+gaim_ssl_connect(account, host, port, func, error_func, data)
+ Gaim::Account account
+ const char *host
+ int port
+ GaimSslInputFunction func
+ GaimSslErrorFunction error_func
+
+void
+gaim_ssl_input_add(gsc, func, data)
+ Gaim::Ssl::Connection gsc
+ Gaim::SslInputFunction func
+
+Gaim::Ssl::Connection
+gaim_ssl_connect_fd(account, fd, func, error_func, data)
+ Gaim::Account account
+ int fd
+ GaimSslInputFunction func
+ GaimSslErrorFunction error_func
+
+*/
+
+MODULE = Gaim::SSL PACKAGE = Gaim::SSL PREFIX = gaim_ssl_
+PROTOTYPES: ENABLE
+
+void
+gaim_ssl_close(gsc)
+ Gaim::Ssl::Connection gsc
+
+Gaim::Ssl::Ops
+gaim_ssl_get_ops()
+
+void
+gaim_ssl_init()
+
+gboolean
+gaim_ssl_is_supported()
+
+size_t
+gaim_ssl_read(gsc, buffer, len)
+ Gaim::Ssl::Connection gsc
+ void * buffer
+ size_t len
+
+void
+gaim_ssl_set_ops(ops)
+ Gaim::Ssl::Ops ops
+
+void
+gaim_ssl_uninit()
+
+size_t
+gaim_ssl_write(gsc, buffer, len)
+ Gaim::Ssl::Connection gsc
+ void * buffer
+ size_t len
diff --git a/libpurple/plugins/perl/common/SavedStatuses.xs b/libpurple/plugins/perl/common/SavedStatuses.xs
new file mode 100644
index 0000000000..8851ed84bb
--- /dev/null
+++ b/libpurple/plugins/perl/common/SavedStatuses.xs
@@ -0,0 +1,58 @@
+#include "module.h"
+
+MODULE = Gaim::SavedStatus PACKAGE = Gaim::SavedStatus PREFIX = gaim_savedstatus_
+PROTOTYPES: ENABLE
+
+gboolean
+gaim_savedstatus_delete(title)
+ const char *title
+
+Gaim::SavedStatus
+gaim_savedstatus_find(title)
+ const char *title
+
+const char *
+gaim_savedstatus_get_message(saved_status)
+ Gaim::SavedStatus saved_status
+
+const char *
+gaim_savedstatus_get_title(saved_status)
+ Gaim::SavedStatus saved_status
+
+Gaim::StatusPrimitive
+gaim_savedstatus_get_type(saved_status)
+ Gaim::SavedStatus saved_status
+
+Gaim::SavedStatus
+gaim_savedstatus_new(title, type)
+ const char *title
+ Gaim::StatusPrimitive type
+
+void
+gaim_savedstatus_set_message(status, message)
+ Gaim::SavedStatus status
+ const char *message
+
+Gaim::SavedStatus
+gaim_savedstatus_get_current()
+
+MODULE = Gaim::SavedStatus PACKAGE = Gaim::SavedStatuses PREFIX = gaim_savedstatuses_
+PROTOTYPES: ENABLE
+
+void
+gaim_savedstatuses_get_all()
+PREINIT:
+ const GList *l;
+PPCODE:
+ for (l = gaim_savedstatuses_get_all(); l != NULL; l = l->next) {
+ XPUSHs(sv_2mortal(gaim_perl_bless_object(l->data, "Gaim::SavedStatus")));
+ }
+
+Gaim::Handle
+gaim_savedstatuses_get_handle()
+
+void
+gaim_savedstatuses_init()
+
+void
+gaim_savedstatuses_uninit()
diff --git a/libpurple/plugins/perl/common/Server.xs b/libpurple/plugins/perl/common/Server.xs
new file mode 100644
index 0000000000..e5fb5027df
--- /dev/null
+++ b/libpurple/plugins/perl/common/Server.xs
@@ -0,0 +1,216 @@
+#include "module.h"
+
+MODULE = Gaim::Serv PACKAGE = Gaim::Serv PREFIX = serv_
+PROTOTYPES: ENABLE
+
+
+void
+serv_add_deny(con, a)
+ Gaim::Connection con
+ const char * a
+
+void
+serv_add_permit(a, b)
+ Gaim::Connection a
+ const char * b
+
+void
+serv_alias_buddy(buddy)
+ Gaim::BuddyList::Buddy buddy
+
+void
+serv_chat_invite(con, a, b, c)
+ Gaim::Connection con
+ int a
+ const char * b
+ const char * c
+
+void
+serv_chat_leave(a, b)
+ Gaim::Connection a
+ int b
+
+int
+serv_chat_send(con, a, b, flags)
+ Gaim::Connection con
+ int a
+ const char * b
+ Gaim::MessageFlags flags
+
+void
+serv_chat_whisper(con, a, b, c)
+ Gaim::Connection con
+ int a
+ const char * b
+ const char * c
+
+void
+serv_get_info(con, a)
+ Gaim::Connection con
+ const char * a
+
+void
+serv_got_alias(gc, who, alias)
+ Gaim::Connection gc
+ const char *who
+ const char *alias
+
+void
+serv_got_chat_in(g, id, who, chatflags, message, mtime)
+ Gaim::Connection g
+ int id
+ const char *who
+ Gaim::MessageFlags chatflags
+ const char *message
+ time_t mtime
+
+void
+serv_got_chat_invite(gc, name, who, message, components)
+ Gaim::Connection gc
+ const char *name
+ const char *who
+ const char *message
+ SV * components
+INIT:
+ HV * t_HV;
+ HE * t_HE;
+ SV * t_SV;
+ GHashTable * t_GHash;
+ I32 len;
+ char *t_key, *t_value;
+CODE:
+ t_HV = (HV *)SvRV(components);
+ t_GHash = g_hash_table_new(g_str_hash, g_str_equal);
+
+ for (t_HE = hv_iternext(t_HV); t_HE != NULL; t_HE = hv_iternext(t_HV) ) {
+ t_key = hv_iterkey(t_HE, &len);
+ t_SV = *hv_fetch(t_HV, t_key, len, 0);
+ t_value = SvPV(t_SV, PL_na);
+
+ g_hash_table_insert(t_GHash, t_key, t_value);
+ }
+ serv_got_chat_invite(gc, name, who, message, t_GHash);
+
+void
+serv_got_chat_left(g, id)
+ Gaim::Connection g
+ int id
+
+void
+serv_got_im(gc, who, msg, imflags, mtime)
+ Gaim::Connection gc
+ const char *who
+ const char *msg
+ Gaim::MessageFlags imflags
+ time_t mtime
+
+Gaim::Conversation
+serv_got_joined_chat(gc, id, name)
+ Gaim::Connection gc
+ int id
+ const char *name
+
+void
+serv_got_typing(gc, name, timeout, state)
+ Gaim::Connection gc
+ const char *name
+ int timeout
+ Gaim::TypingState state
+
+void
+serv_got_typing_stopped(gc, name)
+ Gaim::Connection gc
+ const char *name
+
+void
+serv_join_chat(con, components)
+ Gaim::Connection con
+ SV * components
+INIT:
+ HV * t_HV;
+ HE * t_HE;
+ SV * t_SV;
+ GHashTable * t_GHash;
+ I32 len;
+ char *t_key, *t_value;
+CODE:
+ t_HV = (HV *)SvRV(components);
+ t_GHash = g_hash_table_new(g_str_hash, g_str_equal);
+
+ for (t_HE = hv_iternext(t_HV); t_HE != NULL; t_HE = hv_iternext(t_HV) ) {
+ t_key = hv_iterkey(t_HE, &len);
+ t_SV = *hv_fetch(t_HV, t_key, len, 0);
+ t_value = SvPV(t_SV, PL_na);
+
+ g_hash_table_insert(t_GHash, t_key, t_value);
+ }
+ serv_join_chat(con, t_GHash);
+
+void
+serv_move_buddy(buddy, group1, group2)
+ Gaim::BuddyList::Buddy buddy
+ Gaim::BuddyList::Group group1
+ Gaim::BuddyList::Group group2
+
+void
+serv_reject_chat(con, components)
+ Gaim::Connection con
+ SV * components
+INIT:
+ HV * t_HV;
+ HE * t_HE;
+ SV * t_SV;
+ GHashTable * t_GHash;
+ I32 len;
+ char *t_key, *t_value;
+CODE:
+ t_HV = (HV *)SvRV(components);
+ t_GHash = g_hash_table_new(g_str_hash, g_str_equal);
+
+ for (t_HE = hv_iternext(t_HV); t_HE != NULL; t_HE = hv_iternext(t_HV) ) {
+ t_key = hv_iterkey(t_HE, &len);
+ t_SV = *hv_fetch(t_HV, t_key, len, 0);
+ t_value = SvPV(t_SV, PL_na);
+
+ g_hash_table_insert(t_GHash, t_key, t_value);
+ }
+ serv_reject_chat(con, t_GHash);
+
+void
+serv_rem_deny(con, a)
+ Gaim::Connection con
+ const char * a
+
+void
+serv_rem_permit(con, a)
+ Gaim::Connection con
+ const char * a
+
+void
+serv_send_file(gc, who, file)
+ Gaim::Connection gc
+ const char *who
+ const char *file
+
+int
+serv_send_im(con, a, b, flags )
+ Gaim::Connection con
+ const char * a
+ const char * b
+ Gaim::MessageFlags flags
+
+int
+serv_send_typing(con, a, state)
+ Gaim::Connection con
+ const char * a
+ Gaim::TypingState state
+
+void
+serv_set_info(con, a)
+ Gaim::Connection con
+ const char * a
+
+void
+serv_set_permit_deny(con)
+ Gaim::Connection con
+
diff --git a/libpurple/plugins/perl/common/Signal.xs b/libpurple/plugins/perl/common/Signal.xs
new file mode 100644
index 0000000000..793f1dd223
--- /dev/null
+++ b/libpurple/plugins/perl/common/Signal.xs
@@ -0,0 +1,34 @@
+#include "module.h"
+#include "../perl-handlers.h"
+
+MODULE = Gaim::Signal PACKAGE = Gaim::Signal PREFIX = gaim_signal_
+PROTOTYPES: ENABLE
+
+void
+gaim_signal_connect_priority(instance, signal, plugin, callback, priority, data = 0)
+ Gaim::Handle instance
+ const char *signal
+ Gaim::Plugin plugin
+ SV *callback
+ int priority
+ SV *data
+CODE:
+ gaim_perl_signal_connect(plugin, instance, signal, callback, data, priority);
+
+void
+gaim_signal_connect(instance, signal, plugin, callback, data = 0)
+ Gaim::Handle instance
+ const char *signal
+ Gaim::Plugin plugin
+ SV *callback
+ SV *data
+CODE:
+ gaim_perl_signal_connect(plugin, instance, signal, callback, data, GAIM_SIGNAL_PRIORITY_DEFAULT);
+
+void
+gaim_signal_disconnect(instance, signal, plugin)
+ Gaim::Handle instance
+ const char *signal
+ Gaim::Plugin plugin
+CODE:
+ gaim_perl_signal_disconnect(plugin, instance, signal);
diff --git a/libpurple/plugins/perl/common/Sound.xs b/libpurple/plugins/perl/common/Sound.xs
new file mode 100644
index 0000000000..0f732797c5
--- /dev/null
+++ b/libpurple/plugins/perl/common/Sound.xs
@@ -0,0 +1,27 @@
+#include "module.h"
+
+MODULE = Gaim::Sound PACKAGE = Gaim::Sound PREFIX = gaim_sound_
+PROTOTYPES: ENABLE
+
+Gaim::Sound::UiOps
+gaim_sound_get_ui_ops()
+
+void
+gaim_sound_init()
+
+void
+gaim_sound_play_event(event, account)
+ Gaim::SoundEventID event
+ Gaim::Account account
+
+void
+gaim_sound_play_file(filename, account)
+ const char *filename
+ Gaim::Account account
+
+void
+gaim_sound_set_ui_ops(ops)
+ Gaim::Sound::UiOps ops
+
+void
+gaim_sound_uninit()
diff --git a/libpurple/plugins/perl/common/Status.xs b/libpurple/plugins/perl/common/Status.xs
new file mode 100644
index 0000000000..02ae4b2b4b
--- /dev/null
+++ b/libpurple/plugins/perl/common/Status.xs
@@ -0,0 +1,439 @@
+#include "module.h"
+
+/* TODO
+
+void
+gaim_status_type_add_attrs(status_type, id, name, value, gaim_status_type_add_attrs)
+ Gaim::StatusType status_type
+ const char *id
+ const char *name
+ Gaim::Value value
+ ...
+
+Gaim::StatusType
+gaim_status_type_new_with_attrs(primitive, id, name, saveable, user_settable, independent, attr_id, attr_name, attr_value, gaim_status_type_new_with_attrs)
+ Gaim::StatusPrimitive primitive
+ const char *id
+ const char *name
+ gboolean saveable
+ gboolean user_settable
+ gboolean independent
+ const char *attr_id
+ const char *attr_name
+ Gaim::Value attr_value
+ ...
+
+*/
+
+/* These break on faceprint's amd64 box
+void
+gaim_status_type_add_attrs_vargs(status_type, args)
+ Gaim::StatusType status_type
+ va_list args
+
+void
+gaim_status_set_active_with_attrs(status, active, args)
+ Gaim::Status status
+ gboolean active
+ va_list args
+
+ */
+
+MODULE = Gaim::Status PACKAGE = Gaim::Presence PREFIX = gaim_presence_
+PROTOTYPES: ENABLE
+
+void
+gaim_presence_add_list(presence, source_list)
+ Gaim::Presence presence
+ SV *source_list
+PREINIT:
+ GList *t_GL;
+ int i, t_len;
+PPCODE:
+ t_GL = NULL;
+ t_len = av_len((AV *)SvRV(source_list));
+
+ for (i = 0; i < t_len; i++) {
+ STRLEN t_sl;
+ t_GL = g_list_append(t_GL, SvPV(*av_fetch((AV *)SvRV(source_list), i, 0), t_sl));
+ }
+ gaim_presence_add_list(presence, t_GL);
+
+void
+gaim_presence_add_status(presence, status)
+ Gaim::Presence presence
+ Gaim::Status status
+
+gint
+gaim_presence_compare(presence1, presence2)
+ Gaim::Presence presence1
+ Gaim::Presence presence2
+
+void
+gaim_presence_destroy(presence)
+ Gaim::Presence presence
+
+Gaim::Account
+gaim_presence_get_account(presence)
+ Gaim::Presence presence
+
+Gaim::Status
+gaim_presence_get_active_status(presence)
+ Gaim::Presence presence
+
+void
+gaim_presence_get_buddies(presence)
+ Gaim::Presence presence
+PREINIT:
+ const GList *l;
+PPCODE:
+ for (l = gaim_presence_get_buddies(presence); l != NULL; l = l->next) {
+ XPUSHs(sv_2mortal(gaim_perl_bless_object(l->data, "Gaim::BuddyList::Buddy")));
+ }
+
+const char *
+gaim_presence_get_chat_user(presence)
+ Gaim::Presence presence
+
+Gaim::PresenceContext
+gaim_presence_get_context(presence)
+ Gaim::Presence presence
+
+Gaim::Conversation
+gaim_presence_get_conversation(presence)
+ Gaim::Presence presence
+
+time_t
+gaim_presence_get_idle_time(presence)
+ Gaim::Presence presence
+
+time_t
+gaim_presence_get_login_time(presence)
+ Gaim::Presence presence
+
+Gaim::Status
+gaim_presence_get_status(presence, status_id)
+ Gaim::Presence presence
+ const char *status_id
+
+void
+gaim_presence_get_statuses(presence)
+ Gaim::Presence presence
+PREINIT:
+ const GList *l;
+PPCODE:
+ for (l = gaim_presence_get_statuses(presence); l != NULL; l = l->next) {
+ XPUSHs(sv_2mortal(gaim_perl_bless_object(l->data, "Gaim::Status")));
+ }
+
+gboolean
+gaim_presence_is_available(presence)
+ Gaim::Presence presence
+
+gboolean
+gaim_presence_is_idle(presence)
+ Gaim::Presence presence
+
+gboolean
+gaim_presence_is_online(presence)
+ Gaim::Presence presence
+
+gboolean
+gaim_presence_is_status_active(presence, status_id)
+ Gaim::Presence presence
+ const char *status_id
+
+gboolean
+gaim_presence_is_status_primitive_active(presence, primitive)
+ Gaim::Presence presence
+ Gaim::StatusPrimitive primitive
+
+Gaim::Presence
+gaim_presence_new(context)
+ Gaim::PresenceContext context
+
+Gaim::Presence
+gaim_presence_new_for_account(account)
+ Gaim::Account account
+
+Gaim::Presence
+gaim_presence_new_for_buddy(buddy)
+ Gaim::BuddyList::Buddy buddy
+
+Gaim::Presence
+gaim_presence_new_for_conv(conv)
+ Gaim::Conversation conv
+
+void
+gaim_presence_remove_buddy(presence, buddy)
+ Gaim::Presence presence
+ Gaim::BuddyList::Buddy buddy
+
+void
+gaim_presence_set_idle(presence, idle, idle_time)
+ Gaim::Presence presence
+ gboolean idle
+ time_t idle_time
+
+void
+gaim_presence_set_login_time(presence, login_time)
+ Gaim::Presence presence
+ time_t login_time
+
+void
+gaim_presence_set_status_active(presence, status_id, active)
+ Gaim::Presence presence
+ const char *status_id
+ gboolean active
+
+void
+gaim_presence_switch_status(presence, status_id)
+ Gaim::Presence presence
+ const char *status_id
+
+MODULE = Gaim::Status PACKAGE = Gaim::Primitive PREFIX = gaim_primitive_
+PROTOTYPES: ENABLE
+
+const char *
+gaim_primitive_get_id_from_type(type)
+ Gaim::StatusPrimitive type
+
+const char *
+gaim_primitive_get_name_from_type(type)
+ Gaim::StatusPrimitive type
+
+Gaim::StatusPrimitive
+gaim_primitive_get_type_from_id(id)
+ const char *id
+
+MODULE = Gaim::Status PACKAGE = Gaim::StatusAttr PREFIX = gaim_status_attr_
+PROTOTYPES: ENABLE
+
+void
+gaim_status_attr_destroy(attr)
+ Gaim::StatusAttr attr
+
+const char *
+gaim_status_attr_get_id(attr)
+ Gaim::StatusAttr attr
+
+const char *
+gaim_status_attr_get_name(attr)
+ Gaim::StatusAttr attr
+
+Gaim::Value
+gaim_status_attr_get_value(attr)
+ Gaim::StatusAttr attr
+
+Gaim::StatusAttr
+gaim_status_attr_new(id, name, value_type)
+ const char *id
+ const char *name
+ Gaim::Value value_type
+
+MODULE = Gaim::Status PACKAGE = Gaim::Status PREFIX = gaim_status_
+PROTOTYPES: ENABLE
+
+gint
+gaim_status_compare(status1, status2)
+ Gaim::Status status1
+ Gaim::Status status2
+
+void
+gaim_status_destroy(status)
+ Gaim::Status status
+
+gboolean
+gaim_status_get_attr_boolean(status, id)
+ Gaim::Status status
+ const char *id
+
+int
+gaim_status_get_attr_int(status, id)
+ Gaim::Status status
+ const char *id
+
+const char *
+gaim_status_get_attr_string(status, id)
+ Gaim::Status status
+ const char *id
+
+Gaim::Value
+gaim_status_get_attr_value(status, id)
+ Gaim::Status status
+ const char *id
+
+Gaim::Handle
+gaim_status_get_handle()
+
+const char *
+gaim_status_get_id(status)
+ Gaim::Status status
+
+const char *
+gaim_status_get_name(status)
+ Gaim::Status status
+
+Gaim::Presence
+gaim_status_get_presence(status)
+ Gaim::Status status
+
+Gaim::StatusType
+gaim_status_get_type(status)
+ Gaim::Status status
+
+gboolean
+gaim_status_is_active(status)
+ Gaim::Status status
+
+gboolean
+gaim_status_is_available(status)
+ Gaim::Status status
+
+gboolean
+gaim_status_is_exclusive(status)
+ Gaim::Status status
+
+gboolean
+gaim_status_is_independent(status)
+ Gaim::Status status
+
+gboolean
+gaim_status_is_online(status)
+ Gaim::Status status
+
+Gaim::Status
+gaim_status_new(status_type, presence)
+ Gaim::StatusType status_type
+ Gaim::Presence presence
+
+void
+gaim_status_set_active(status, active)
+ Gaim::Status status
+ gboolean active
+
+void
+gaim_status_set_attr_boolean(status, id, value)
+ Gaim::Status status
+ const char *id
+ gboolean value
+
+void
+gaim_status_set_attr_string(status, id, value)
+ Gaim::Status status
+ const char *id
+ const char *value
+
+void
+gaim_status_init()
+
+void
+gaim_status_uninit()
+
+MODULE = Gaim::Status PACKAGE = Gaim::StatusType PREFIX = gaim_status_type_
+PROTOTYPES: ENABLE
+
+void
+gaim_status_type_add_attr(status_type, id, name, value)
+ Gaim::StatusType status_type
+ const char *id
+ const char *name
+ Gaim::Value value
+
+void
+gaim_status_type_destroy(status_type)
+ Gaim::StatusType status_type
+
+Gaim::StatusType
+gaim_status_type_find_with_id(status_types, id)
+ SV *status_types
+ const char *id
+PREINIT:
+/* XXX Check that this function actually works, I think it might need a */
+/* status_type as it's first argument to work as $status_type->find_with_id */
+/* properly. */
+ GList *t_GL;
+ int i, t_len;
+CODE:
+ t_GL = NULL;
+ t_len = av_len((AV *)SvRV(status_types));
+
+ for (i = 0; i < t_len; i++) {
+ STRLEN t_sl;
+ t_GL = g_list_append(t_GL, SvPV(*av_fetch((AV *)SvRV(status_types), i, 0), t_sl));
+ }
+ RETVAL = (GaimStatusType *)gaim_status_type_find_with_id(t_GL, id);
+OUTPUT:
+ RETVAL
+
+Gaim::StatusAttr
+gaim_status_type_get_attr(status_type, id)
+ Gaim::StatusType status_type
+ const char *id
+
+void
+gaim_status_type_get_attrs(status_type)
+ Gaim::StatusType status_type
+PREINIT:
+ const GList *l;
+PPCODE:
+ for (l = gaim_status_type_get_attrs(status_type); l != NULL; l = l->next) {
+ XPUSHs(sv_2mortal(gaim_perl_bless_object(l->data, "Gaim::StatusAttr")));
+ }
+
+const char *
+gaim_status_type_get_id(status_type)
+ Gaim::StatusType status_type
+
+const char *
+gaim_status_type_get_name(status_type)
+ Gaim::StatusType status_type
+
+const char *
+gaim_status_type_get_primary_attr(status_type)
+ Gaim::StatusType status_type
+
+Gaim::StatusPrimitive
+gaim_status_type_get_primitive(status_type)
+ Gaim::StatusType status_type
+
+gboolean
+gaim_status_type_is_available(status_type)
+ Gaim::StatusType status_type
+
+gboolean
+gaim_status_type_is_exclusive(status_type)
+ Gaim::StatusType status_type
+
+gboolean
+gaim_status_type_is_independent(status_type)
+ Gaim::StatusType status_type
+
+gboolean
+gaim_status_type_is_saveable(status_type)
+ Gaim::StatusType status_type
+
+gboolean
+gaim_status_type_is_user_settable(status_type)
+ Gaim::StatusType status_type
+
+Gaim::StatusType
+gaim_status_type_new(primitive, id, name, user_settable)
+ Gaim::StatusPrimitive primitive
+ const char *id
+ const char *name
+ gboolean user_settable
+
+Gaim::StatusType
+gaim_status_type_new_full(primitive, id, name, saveable, user_settable, independent)
+ Gaim::StatusPrimitive primitive
+ const char *id
+ const char *name
+ gboolean saveable
+ gboolean user_settable
+ gboolean independent
+
+void
+gaim_status_type_set_primary_attr(status_type, attr_id)
+ Gaim::StatusType status_type
+ const char *attr_id
diff --git a/libpurple/plugins/perl/common/Stringref.xs b/libpurple/plugins/perl/common/Stringref.xs
new file mode 100644
index 0000000000..710cab42ec
--- /dev/null
+++ b/libpurple/plugins/perl/common/Stringref.xs
@@ -0,0 +1,37 @@
+#include "module.h"
+
+MODULE = Gaim::Stringref PACKAGE = Gaim::Stringref PREFIX = gaim_stringref_
+PROTOTYPES: ENABLE
+
+int
+gaim_stringref_cmp(s1, s2)
+ Gaim::Stringref s1
+ Gaim::Stringref s2
+
+size_t
+gaim_stringref_len(stringref)
+ Gaim::Stringref stringref
+
+Gaim::Stringref
+gaim_stringref_new(class, value)
+ const char *value
+ C_ARGS:
+ value
+
+Gaim::Stringref
+gaim_stringref_new_noref(class, value)
+ const char *value
+ C_ARGS:
+ value
+
+Gaim::Stringref
+gaim_stringref_ref(stringref)
+ Gaim::Stringref stringref
+
+void
+gaim_stringref_unref(stringref)
+ Gaim::Stringref stringref
+
+const char *
+gaim_stringref_value(stringref)
+ Gaim::Stringref stringref
diff --git a/libpurple/plugins/perl/common/Util.xs b/libpurple/plugins/perl/common/Util.xs
new file mode 100644
index 0000000000..815507d7a8
--- /dev/null
+++ b/libpurple/plugins/perl/common/Util.xs
@@ -0,0 +1,267 @@
+#include "module.h"
+
+typedef struct {
+ char *cb;
+} GaimPerlUrlData;
+
+static void gaim_perl_util_url_cb(GaimUtilFetchUrlData *url_data, void *user_data, const gchar *url_text, size_t size, const gchar *error_message) {
+ GaimPerlUrlData *gpr = (GaimPerlUrlData *)user_data;
+ dSP;
+ ENTER;
+ SAVETMPS;
+ PUSHMARK(SP);
+
+ XPUSHs(sv_2mortal(newSVpvn(url_text, size)));
+ PUTBACK;
+
+ call_pv(gpr->cb, G_EVAL | G_SCALAR);
+ SPAGAIN;
+
+ g_free(gpr->cb);
+ g_free(gpr);
+
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+}
+
+MODULE = Gaim::Util PACKAGE = Gaim::Util PREFIX = gaim_
+PROTOTYPES: ENABLE
+
+void
+gaim_util_fetch_url(handle, url, full, user_agent, http11, cb)
+ Gaim::Plugin handle
+ const char *url
+ gboolean full
+ const char *user_agent
+ gboolean http11
+ SV * cb
+CODE:
+ GaimPerlUrlData *gpr;
+ STRLEN len;
+ char *basename;
+
+ basename = g_path_get_basename(handle->path);
+ gaim_perl_normalize_script_name(basename);
+ gpr = g_new(GaimPerlUrlData, 1);
+
+ gpr->cb = g_strdup_printf("Gaim::Script::%s::%s", basename, SvPV(cb, len));
+ g_free(basename);
+ gaim_util_fetch_url(url, full, user_agent, http11, gaim_perl_util_url_cb, gpr);
+
+int
+gaim_build_dir(path, mode)
+ const char *path
+ int mode
+
+const char *
+gaim_date_format_full(tm)
+ const struct tm *tm
+
+const char *
+gaim_date_format_long(tm)
+ const struct tm *tm
+
+const char *
+gaim_date_format_short(tm)
+ const struct tm *tm
+
+gboolean
+gaim_email_is_valid(address)
+ const char *address
+
+const char *
+gaim_escape_filename(str)
+ const char *str
+
+gchar_own *
+gaim_fd_get_ip(fd)
+ int fd
+
+const gchar *
+gaim_home_dir()
+
+gboolean
+gaim_markup_extract_info_field(str, len, user_info, start_token, skip, end_token, check_value, no_value_token, display_name, is_link, link_prefix, format_cb)
+ const char *str
+ int len
+ Gaim::NotifyUserInfo user_info
+ const char *start_token
+ int skip
+ const char *end_token
+ char check_value
+ const char *no_value_token
+ const char *display_name
+ gboolean is_link
+ const char *link_prefix
+ Gaim::Util::InfoFieldFormatCallback format_cb
+
+gboolean
+gaim_markup_find_tag(needle, haystack, start, end, attributes)
+ const char *needle
+ const char *haystack
+ const char **start
+ const char **end
+ GData **attributes
+
+gchar_own *
+gaim_markup_get_tag_name(tag)
+ const char *tag
+
+void
+gaim_markup_html_to_xhtml(html, dest_xhtml, dest_plain)
+ const char *html
+ char **dest_xhtml
+ char **dest_plain
+
+gchar_own *
+gaim_markup_linkify(str)
+ const char *str
+
+gchar_own *
+gaim_markup_slice(str, x, y)
+ const char *str
+ guint x
+ guint y
+
+gchar_own *
+gaim_markup_strip_html(str)
+ const char *str
+
+gboolean
+gaim_message_meify(message, len)
+ char *message
+ size_t len
+
+FILE *
+gaim_mkstemp(path, binary)
+ char **path
+ gboolean binary
+
+const char *
+gaim_normalize(account, str)
+ Gaim::Account account
+ const char *str
+
+gboolean
+gaim_program_is_valid(program)
+ const char *program
+
+gchar_own *
+gaim_str_add_cr(str)
+ const char *str
+
+gchar_own *
+gaim_str_binary_to_ascii(binary, len)
+ const unsigned char *binary
+ guint len
+
+gboolean
+gaim_str_has_prefix(s, p)
+ const char *s
+ const char *p
+
+gboolean
+gaim_str_has_suffix(s, x)
+ const char *s
+ const char *x
+
+gchar_own *
+gaim_str_seconds_to_string(sec)
+ guint sec
+
+gchar_own *
+gaim_str_size_to_units(size)
+ size_t size
+
+void
+gaim_str_strip_char(str, thechar)
+ char *str
+ char thechar
+
+time_t
+gaim_str_to_time(timestamp, utc = FALSE, tm = NULL, tz_off = NULL, rest = NULL)
+ const char *timestamp
+ gboolean utc
+ struct tm *tm
+ long *tz_off
+ const char **rest
+
+gchar_own *
+gaim_strcasereplace(string, delimiter, replacement)
+ const char *string
+ const char *delimiter
+ const char *replacement
+
+const char *
+gaim_strcasestr(haystack, needle)
+ const char *haystack
+ const char *needle
+
+gchar_own *
+gaim_strdup_withhtml(src)
+ const gchar *src
+
+gchar_own *
+gaim_strreplace(string, delimiter, replacement)
+ const char *string
+ const char *delimiter
+ const char *replacement
+
+gchar_own *
+gaim_text_strip_mnemonic(in)
+ const char *in
+
+time_t
+gaim_time_build(year, month, day, hour, min, sec)
+ int year
+ int month
+ int day
+ int hour
+ int min
+ int sec
+
+const char *
+gaim_time_format(tm)
+ const struct tm *tm
+
+const char *
+gaim_unescape_filename(str)
+ const char *str
+
+gchar_own *
+gaim_unescape_html(html)
+ const char *html
+
+const char *
+gaim_url_decode(str)
+ const char *str
+
+const char *
+gaim_url_encode(str)
+ const char *str
+
+gboolean
+gaim_url_parse(url, ret_host, ret_port, ret_path, ret_user, ret_passwd)
+ const char *url
+ char **ret_host
+ int *ret_port
+ char **ret_path
+ char **ret_user
+ char **ret_passwd
+
+const char *
+gaim_user_dir()
+
+const char *
+gaim_utf8_strftime(const char *format, const struct tm *tm);
+
+void
+gaim_util_set_user_dir(dir)
+ const char *dir
+
+gboolean
+gaim_util_write_data_to_file(filename, data, size)
+ const char *filename
+ const char *data
+ size_t size
diff --git a/libpurple/plugins/perl/common/XMLNode.xs b/libpurple/plugins/perl/common/XMLNode.xs
new file mode 100644
index 0000000000..601b9f36a6
--- /dev/null
+++ b/libpurple/plugins/perl/common/XMLNode.xs
@@ -0,0 +1,88 @@
+#include "module.h"
+
+MODULE = Gaim::XMLNode PACKAGE = Gaim::XMLNode PREFIX = xmlnode_
+PROTOTYPES: ENABLE
+
+Gaim::XMLNode
+xmlnode_copy(class, src)
+ Gaim::XMLNode src
+ C_ARGS:
+ src
+
+void
+xmlnode_free(node)
+ Gaim::XMLNode node
+
+Gaim::XMLNode
+xmlnode_from_str(class, str, size)
+ const char *str
+ gssize size
+ C_ARGS:
+ str, size
+
+const char *
+xmlnode_get_attrib(node, attr)
+ Gaim::XMLNode node
+ const char *attr
+
+Gaim::XMLNode
+xmlnode_get_child(parent, name)
+ Gaim::XMLNode parent
+ const char *name
+
+Gaim::XMLNode
+xmlnode_get_child_with_namespace(parent, name, xmlns)
+ Gaim::XMLNode parent
+ const char *name
+ const char *xmlns
+
+gchar_own *
+xmlnode_get_data(node)
+ Gaim::XMLNode node
+
+Gaim::XMLNode
+xmlnode_get_next_twin(node)
+ Gaim::XMLNode node
+
+void
+xmlnode_insert_child(parent, child)
+ Gaim::XMLNode parent
+ Gaim::XMLNode child
+
+void
+xmlnode_insert_data(node, data, size)
+ Gaim::XMLNode node
+ const char *data
+ gssize size
+
+Gaim::XMLNode
+xmlnode_new(class, name)
+ const char *name
+ C_ARGS:
+ name
+
+Gaim::XMLNode
+xmlnode_new_child(parent, name)
+ Gaim::XMLNode parent
+ const char *name
+
+void
+xmlnode_remove_attrib(node, attr)
+ Gaim::XMLNode node
+ const char *attr
+
+void
+xmlnode_set_attrib(node, attr, value)
+ Gaim::XMLNode node
+ const char *attr
+ const char *value
+
+gchar_own *
+xmlnode_to_formatted_str(node, len)
+ Gaim::XMLNode node
+ int *len
+
+gchar_own *
+xmlnode_to_str(node, len)
+ Gaim::XMLNode node
+ int *len
diff --git a/libpurple/plugins/perl/common/fallback/const-c.inc b/libpurple/plugins/perl/common/fallback/const-c.inc
new file mode 100644
index 0000000000..4e1290d661
--- /dev/null
+++ b/libpurple/plugins/perl/common/fallback/const-c.inc
@@ -0,0 +1,115 @@
+#define PERL_constant_NOTFOUND 1
+#define PERL_constant_NOTDEF 2
+#define PERL_constant_ISIV 3
+#define PERL_constant_ISNO 4
+#define PERL_constant_ISNV 5
+#define PERL_constant_ISPV 6
+#define PERL_constant_ISPVN 7
+#define PERL_constant_ISSV 8
+#define PERL_constant_ISUNDEF 9
+#define PERL_constant_ISUV 10
+#define PERL_constant_ISYES 11
+
+#ifndef NVTYPE
+typedef double NV; /* 5.6 and later define NVTYPE, and typedef NV to it. */
+#endif
+#ifndef aTHX_
+#define aTHX_ /* 5.6 or later define this for threading support. */
+#endif
+#ifndef pTHX_
+#define pTHX_ /* 5.6 or later define this for threading support. */
+#endif
+
+static int
+constant (pTHX_ const char *name, STRLEN len, IV *iv_return) {
+ /* Initially switch on the length of the name. */
+ /* When generated this function returned values for the list of names given
+ in this section of perl code. Rather than manually editing these functions
+ to add or remove constants, which would result in this comment and section
+ of code becoming inaccurate, we recommend that you edit this section of
+ code, and use it to regenerate a new set of constant functions which you
+ then use to replace the originals.
+
+ Regenerate these constant functions by feeding this entire source file to
+ perl -x
+
+#!/usr/bin/perl -w
+use ExtUtils::Constant qw (constant_types C_constant XS_constant);
+
+my $types = {map {($_, 1)} qw(IV)};
+my @names = (qw(),
+ {name=>"GAIM_DEBUG_ALL", type=>"IV", macro=>"1"},
+ {name=>"GAIM_DEBUG_ERROR", type=>"IV", macro=>"1"},
+ {name=>"GAIM_DEBUG_FATAL", type=>"IV", macro=>"1"},
+ {name=>"GAIM_DEBUG_INFO", type=>"IV", macro=>"1"},
+ {name=>"GAIM_DEBUG_MISC", type=>"IV", macro=>"1"},
+ {name=>"GAIM_DEBUG_WARNING", type=>"IV", macro=>"1"});
+
+print constant_types(); # macro defs
+foreach (C_constant ("Gaim::DebugLevel", 'constant', 'IV', $types, undef, 3, @names) ) {
+ print $_, "\n"; # C constant subs
+}
+print "#### XS Section:\n";
+print XS_constant ("Gaim::DebugLevel", $types);
+__END__
+ */
+
+ switch (len) {
+ case 14:
+ if (memEQ(name, "GAIM_DEBUG_ALL", 14)) {
+ *iv_return = GAIM_DEBUG_ALL;
+ return PERL_constant_ISIV;
+ }
+ break;
+ case 15:
+ /* Names all of length 15. */
+ /* GAIM_DEBUG_INFO GAIM_DEBUG_MISC */
+ /* Offset 11 gives the best switch position. */
+ switch (name[11]) {
+ case 'I':
+ if (memEQ(name, "GAIM_DEBUG_INFO", 15)) {
+ /* ^ */
+ *iv_return = GAIM_DEBUG_INFO;
+ return PERL_constant_ISIV;
+ }
+ break;
+ case 'M':
+ if (memEQ(name, "GAIM_DEBUG_MISC", 15)) {
+ /* ^ */
+ *iv_return = GAIM_DEBUG_MISC;
+ return PERL_constant_ISIV;
+ }
+ break;
+ }
+ break;
+ case 16:
+ /* Names all of length 16. */
+ /* GAIM_DEBUG_ERROR GAIM_DEBUG_FATAL */
+ /* Offset 11 gives the best switch position. */
+ switch (name[11]) {
+ case 'E':
+ if (memEQ(name, "GAIM_DEBUG_ERROR", 16)) {
+ /* ^ */
+ *iv_return = GAIM_DEBUG_ERROR;
+ return PERL_constant_ISIV;
+ }
+ break;
+ case 'F':
+ if (memEQ(name, "GAIM_DEBUG_FATAL", 16)) {
+ /* ^ */
+ *iv_return = GAIM_DEBUG_FATAL;
+ return PERL_constant_ISIV;
+ }
+ break;
+ }
+ break;
+ case 18:
+ if (memEQ(name, "GAIM_DEBUG_WARNING", 18)) {
+ *iv_return = GAIM_DEBUG_WARNING;
+ return PERL_constant_ISIV;
+ }
+ break;
+ }
+ return PERL_constant_NOTFOUND;
+}
+
diff --git a/libpurple/plugins/perl/common/fallback/const-xs.inc b/libpurple/plugins/perl/common/fallback/const-xs.inc
new file mode 100644
index 0000000000..45384ff63b
--- /dev/null
+++ b/libpurple/plugins/perl/common/fallback/const-xs.inc
@@ -0,0 +1,88 @@
+void
+constant(sv)
+ PREINIT:
+#ifdef dXSTARG
+ dXSTARG; /* Faster if we have it. */
+#else
+ dTARGET;
+#endif
+ STRLEN len;
+ int type;
+ IV iv;
+ /* NV nv; Uncomment this if you need to return NVs */
+ /* const char *pv; Uncomment this if you need to return PVs */
+ INPUT:
+ SV * sv;
+ const char * s = SvPV(sv, len);
+ PPCODE:
+ /* Change this to constant(aTHX_ s, len, &iv, &nv);
+ if you need to return both NVs and IVs */
+ type = constant(aTHX_ s, len, &iv);
+ /* Return 1 or 2 items. First is error message, or undef if no error.
+ Second, if present, is found value */
+ switch (type) {
+ case PERL_constant_NOTFOUND:
+ sv = sv_2mortal(newSVpvf("%s is not a valid Gaim::DebugLevel macro", s));
+ PUSHs(sv);
+ break;
+ case PERL_constant_NOTDEF:
+ sv = sv_2mortal(newSVpvf(
+ "Your vendor has not defined Gaim::DebugLevel macro %s, used", s));
+ PUSHs(sv);
+ break;
+ case PERL_constant_ISIV:
+ EXTEND(SP, 1);
+ PUSHs(&PL_sv_undef);
+ PUSHi(iv);
+ break;
+ /* Uncomment this if you need to return NOs
+ case PERL_constant_ISNO:
+ EXTEND(SP, 1);
+ PUSHs(&PL_sv_undef);
+ PUSHs(&PL_sv_no);
+ break; */
+ /* Uncomment this if you need to return NVs
+ case PERL_constant_ISNV:
+ EXTEND(SP, 1);
+ PUSHs(&PL_sv_undef);
+ PUSHn(nv);
+ break; */
+ /* Uncomment this if you need to return PVs
+ case PERL_constant_ISPV:
+ EXTEND(SP, 1);
+ PUSHs(&PL_sv_undef);
+ PUSHp(pv, strlen(pv));
+ break; */
+ /* Uncomment this if you need to return PVNs
+ case PERL_constant_ISPVN:
+ EXTEND(SP, 1);
+ PUSHs(&PL_sv_undef);
+ PUSHp(pv, iv);
+ break; */
+ /* Uncomment this if you need to return SVs
+ case PERL_constant_ISSV:
+ EXTEND(SP, 1);
+ PUSHs(&PL_sv_undef);
+ PUSHs(sv);
+ break; */
+ /* Uncomment this if you need to return UNDEFs
+ case PERL_constant_ISUNDEF:
+ break; */
+ /* Uncomment this if you need to return UVs
+ case PERL_constant_ISUV:
+ EXTEND(SP, 1);
+ PUSHs(&PL_sv_undef);
+ PUSHu((UV)iv);
+ break; */
+ /* Uncomment this if you need to return YESs
+ case PERL_constant_ISYES:
+ EXTEND(SP, 1);
+ PUSHs(&PL_sv_undef);
+ PUSHs(&PL_sv_yes);
+ break; */
+ default:
+ sv = sv_2mortal(newSVpvf(
+ "Unexpected return type %d while processing Gaim::DebugLevel macro %s, used",
+ type, s));
+ PUSHs(sv);
+ }
diff --git a/libpurple/plugins/perl/common/module.h b/libpurple/plugins/perl/common/module.h
new file mode 100644
index 0000000000..53357c1a8f
--- /dev/null
+++ b/libpurple/plugins/perl/common/module.h
@@ -0,0 +1,278 @@
+
+
+typedef struct group *Gaim__Group;
+
+#define group perl_group
+
+#include <glib.h>
+#ifdef _WIN32
+#undef pipe
+#endif
+#include <EXTERN.h>
+#include <perl.h>
+#include <XSUB.h>
+
+#undef group
+
+#include "../perl-common.h"
+
+#include "account.h"
+#include "accountopt.h"
+#include "blist.h"
+#include "buddyicon.h"
+#include "cipher.h"
+#include "cmds.h"
+#include "connection.h"
+#include "conversation.h"
+#include "core.h"
+#include "debug.h"
+#include "desktopitem.h"
+#include "eventloop.h"
+#include "ft.h"
+#ifdef GAIM_GTKPERL
+#include "gtkaccount.h"
+#include "gtkblist.h"
+#include "gtkconn.h"
+#include "gtkconv.h"
+#include "gtkutils.h"
+#endif
+#include "imgstore.h"
+#include "network.h"
+#include "notify.h"
+#include "plugin.h"
+#include "pluginpref.h"
+#include "pounce.h"
+#include "prefs.h"
+#include "privacy.h"
+#include "prpl.h"
+#include "proxy.h"
+#include "request.h"
+#include "roomlist.h"
+#include "savedstatuses.h"
+#include "server.h"
+#include "signals.h"
+#include "sound.h"
+#include "sslconn.h"
+#include "status.h"
+#include "stringref.h"
+/* Ewww. perl has it's own util.h which is in the include path :( */
+#include "libgaim/util.h"
+#include "value.h"
+#include "xmlnode.h"
+
+/* account.h */
+typedef GaimAccount * Gaim__Account;
+typedef GaimAccountOption * Gaim__Account__Option;
+typedef GaimAccountUiOps * Gaim__Account__UiOps;
+typedef GaimAccountUserSplit * Gaim__Account__UserSplit;
+
+/* blist.h */
+typedef GaimBlistNode * Gaim__BuddyList__Node;
+typedef GaimBlistNodeFlags Gaim__BuddyList__NodeFlags;
+typedef GaimBlistUiOps * Gaim__BuddyList__UiOps;
+typedef GaimBuddyList * Gaim__BuddyList;
+typedef GaimBuddy * Gaim__BuddyList__Buddy;
+typedef GaimChat * Gaim__BuddyList__Chat;
+typedef GaimContact * Gaim__BuddyList__Contact;
+typedef GaimGroup * Gaim__BuddyList__Group;
+
+/* buddyicon.h */
+typedef GaimBuddyIcon * Gaim__Buddy__Icon;
+
+/* cipher.h */
+typedef GaimCipher * Gaim__Cipher;
+typedef GaimCipherCaps Gaim__CipherCaps;
+typedef GaimCipherContext * Gaim__Cipher__Context;
+typedef GaimCipherOps * Gaim__Cipher__Ops;
+
+/* cmds.h */
+typedef GaimCmdFlag Gaim__Cmd__Flag;
+typedef GaimCmdId Gaim__Cmd__Id;
+typedef GaimCmdPriority Gaim__Cmd__Priority;
+typedef GaimCmdRet Gaim__Cmd__Ret;
+
+/* connection.h */
+typedef GaimConnection * Gaim__Connection;
+typedef GaimConnectionFlags Gaim__ConnectionFlags;
+typedef GaimConnectionState Gaim__ConnectionState;
+typedef GaimConnectionUiOps * Gaim__Connection__UiOps;
+
+/* conversation.h */
+typedef GaimConversationType Gaim__ConversationType;
+typedef GaimConvUpdateType Gaim__ConvUpdateType;
+typedef GaimTypingState Gaim__TypingState;
+typedef GaimMessageFlags Gaim__MessageFlags;
+typedef GaimConvChatBuddyFlags Gaim__ConvChatBuddyFlags;
+typedef GaimConversation * Gaim__Conversation;
+typedef GaimConversationUiOps * Gaim__Conversation__UiOps;
+typedef GaimConvIm * Gaim__Conversation__IM;
+typedef GaimConvChat * Gaim__Conversation__Chat;
+typedef GaimConvChatBuddy * Gaim__Conversation__ChatBuddy;
+
+/* core.h */
+
+typedef GaimCore * Gaim__Core;
+typedef GaimCoreUiOps * Gaim__Core__UiOps;
+
+/* debug.h */
+typedef GaimDebugLevel Gaim__DebugLevel;
+
+/* desktopitem.h */
+typedef GaimDesktopItem * Gaim__DesktopItem;
+typedef GaimDesktopItemType Gaim__DesktopItemType;
+
+/* eventloop.h */
+typedef GaimInputCondition * Gaim__InputCondition;
+typedef GaimEventLoopUiOps * Gaim__EventLoopUiOps;
+
+/* ft.h */
+typedef GaimXfer * Gaim__Xfer;
+typedef GaimXferType Gaim__XferType;
+typedef GaimXferStatusType Gaim__XferStatusType;
+typedef GaimXferUiOps * Gaim__XferUiOps;
+
+#ifdef GAIM_GTKPERL
+/* gtkblish.h */
+typedef GaimGtkBuddyList * Gaim__GTK__BuddyList;
+typedef GaimStatusIconSize Gaim__StatusIconSize;
+
+/* gtkutils.h */
+typedef GaimButtonOrientation Gaim__ButtonOrientation;
+typedef GaimButtonStyle Gaim__ButtonStyle;
+#ifndef _WIN32
+typedef GaimBrowserPlace Gaim__BrowserPlace;
+#endif /* _WIN32 */
+
+/* gtkconv.h */
+typedef GaimUnseenState Gaim__UnseenState;
+typedef GaimGtkConversation * Gaim__GTK__Conversation;
+typedef GdkPixbuf * Gaim__GDK__Pixbuf;
+typedef GtkWidget * Gaim__GTK__Widget;
+
+/* gtkutils.h */
+typedef GtkFileSelection * Gaim__GTK__FileSelection;
+typedef GtkSelectionData * Gaim__GTK__SelectionData;
+typedef GtkTextView * Gaim__GTK__TextView;
+
+/* gtkconn.h */
+#endif
+
+/* imgstore.h */
+typedef GaimStoredImage * Gaim__StoredImage;
+
+/* log.h */
+typedef GaimLog * Gaim__Log;
+typedef GaimLogCommonLoggerData * Gaim__LogCommonLoggerData;
+typedef GaimLogLogger * Gaim__Log__Logger;
+typedef GaimLogReadFlags * Gaim__Log__ReadFlags;
+typedef GaimLogSet * Gaim__LogSet;
+typedef GaimLogType Gaim__LogType;
+
+/* network.h */
+typedef GaimNetworkListenData * Gaim__NetworkListenData;
+typedef GaimNetworkListenCallback Gaim__NetworkListenCallback;
+
+/* notify.h */
+typedef GaimNotifyCloseCallback Gaim__NotifyCloseCallback;
+typedef GaimNotifyMsgType Gaim__NotifyMsgType;
+typedef GaimNotifySearchButtonType Gaim__NotifySearchButtonType;
+typedef GaimNotifySearchResults * Gaim__NotifySearchResults;
+typedef GaimNotifySearchColumn * Gaim__NotifySearchColumn;
+typedef GaimNotifySearchButton * Gaim__NotifySearchButton;
+typedef GaimNotifyType Gaim__NotifyType;
+typedef GaimNotifyUiOps * Gaim__NotifyUiOps;
+typedef GaimNotifyUserInfo * Gaim__NotifyUserInfo;
+typedef GaimNotifyUserInfoEntry * Gaim__NotifyUserInfoEntry;
+
+/* plugin.h */
+typedef GaimPlugin * Gaim__Plugin;
+typedef GaimPluginAction * Gaim__Plugin__Action;
+typedef GaimPluginInfo * Gaim__PluginInfo;
+typedef GaimPluginLoaderInfo * Gaim__PluginLoaderInfo;
+typedef GaimPluginType Gaim__PluginType;
+typedef GaimPluginUiInfo * Gaim__PluginUiInfo;
+
+/* pluginpref.h */
+typedef GaimPluginPref * Gaim__PluginPref;
+typedef GaimPluginPrefFrame * Gaim__PluginPref__Frame;
+typedef GaimPluginPrefType Gaim__PluginPrefType;
+
+/* pounce.h */
+typedef GaimPounce * Gaim__Pounce;
+typedef GaimPounceEvent Gaim__PounceEvent;
+
+/* prefs.h */
+typedef GaimPrefType Gaim__PrefType;
+
+/* privacy.h */
+typedef GaimPrivacyType Gaim__PrivacyType;
+typedef GaimPrivacyUiOps * Gaim__Privacy__UiOps;
+
+/* proxy.h */
+typedef GaimProxyInfo * Gaim__ProxyInfo;
+typedef GaimProxyType Gaim__ProxyType;
+
+/* prpl.h */
+typedef GaimBuddyIconSpec * Gaim__Buddy__Icon__Spec;
+typedef GaimIconScaleRules Gaim__IconScaleRules;
+typedef GaimPluginProtocolInfo * Gaim__PluginProtocolInfo;
+typedef GaimProtocolOptions Gaim__ProtocolOptions;
+
+/* request.h */
+typedef GaimRequestField * Gaim__Request__Field;
+typedef GaimRequestFields * Gaim__Request__Fields;
+typedef GaimRequestFieldGroup * Gaim__Request__Field__Group;
+typedef GaimRequestFieldType Gaim__RequestFieldType;
+typedef GaimRequestType Gaim__RequestType;
+typedef GaimRequestUiOps * Gaim__Request__UiOps;
+
+/* roomlist.h */
+typedef GaimRoomlist * Gaim__Roomlist;
+typedef GaimRoomlistField * Gaim__Roomlist__Field;
+typedef GaimRoomlistFieldType Gaim__RoomlistFieldType;
+typedef GaimRoomlistRoom * Gaim__Roomlist__Room;
+typedef GaimRoomlistRoomType Gaim__RoomlistRoomType;
+typedef GaimRoomlistUiOps * Gaim__Roomlist__UiOps;
+
+/* savedstatuses.h */
+typedef GaimSavedStatus * Gaim__SavedStatus;
+typedef GaimSavedStatusSub * Gaim__SavedStatusSub;
+
+/* sound.h */
+typedef GaimSoundEventID Gaim__SoundEventID;
+typedef GaimSoundUiOps * Gaim__Sound__UiOps;
+
+/* sslconn.h */
+typedef GaimInputCondition * Gaim__Input__Condition;
+typedef GaimSslConnection * Gaim__Ssl__Connection;
+typedef GaimSslErrorType Gaim__SslErrorType;
+typedef GaimSslOps * Gaim__Ssl__Ops;
+
+/* status.h */
+typedef GaimPresence * Gaim__Presence;
+typedef GaimPresenceContext Gaim__PresenceContext;
+typedef GaimStatus * Gaim__Status;
+typedef GaimStatusAttr * Gaim__StatusAttr;
+typedef GaimStatusPrimitive Gaim__StatusPrimitive;
+typedef GaimStatusType * Gaim__StatusType;
+
+/* stringref.h */
+typedef GaimStringref * Gaim__Stringref;
+
+/* util.h */
+typedef GaimInfoFieldFormatCallback Gaim__Util__InfoFieldFormatCallback;
+typedef GaimUtilFetchUrlData Gaim__Util__FetchUrlData;
+typedef GaimMenuAction * Gaim__Menu__Action;
+
+/* value.h */
+typedef GaimValue * Gaim__Value;
+
+/* xmlnode.h */
+typedef xmlnode * Gaim__XMLNode;
+typedef XMLNodeType XMLNode__Type;
+
+/* other */
+typedef void * Gaim__Handle;
+
+typedef gchar gchar_own;
+
diff --git a/libpurple/plugins/perl/common/typemap b/libpurple/plugins/perl/common/typemap
new file mode 100644
index 0000000000..e1eba3412f
--- /dev/null
+++ b/libpurple/plugins/perl/common/typemap
@@ -0,0 +1,209 @@
+TYPEMAP
+guint T_IV
+gint T_IV
+const gint * T_PTR
+const guint * T_PTR
+const guint8 * T_PTR
+guint8 T_IV
+guint8 * T_PTR
+time_t T_IV
+gboolean T_BOOL
+gpointer T_GaimObj
+gconstpointer T_PTR
+const gchar * T_PV
+const char * T_PV
+const char ** T_PTR
+char ** T_PTR
+gchar T_IV
+gchar * T_PV
+gchar_own * T_GCHAR_OWN
+guchar T_IV
+guchar * T_PTR
+guchar ** T_PTR
+const guchar * T_PV
+char * T_PV
+int * T_PTR
+long * T_PTR
+size_t * T_PTR
+GCallback T_PTR
+va_list T_PTR
+GString * T_PTR
+GData * T_PTR
+GData ** T_PTR
+const unsigned char * T_PTR
+struct tm * T_PTR
+const struct tm * T_PTR
+xmlnode * T_PTR
+const xmlnode * T_PTR
+gssize T_IV
+const void * T_PTR
+
+Gaim::Account T_GaimObj
+Gaim::Account::Option T_GaimObj
+Gaim::Account::UiOps T_GaimObj
+Gaim::Account::UserSplit T_GaimObj
+
+Gaim::Buddy::Icon T_GaimObj
+Gaim::Buddy::Icon::Spec T_GaimObj
+Gaim::BuddyList T_GaimObj
+Gaim::BuddyList::Buddy T_GaimObj
+Gaim::BuddyList::Chat T_GaimObj
+Gaim::BuddyList::Contact T_GaimObj
+Gaim::BuddyList::Group T_GaimObj
+Gaim::BuddyList::Node T_GaimObj
+Gaim::BuddyList::NodeFlags T_IV
+Gaim::BuddyList::UiOps T_GaimObj
+
+Gaim::Cipher T_GaimObj
+Gaim::CipherCaps T_IV
+Gaim::Cipher::Ops T_GaimObj
+Gaim::Cipher::Context T_GaimObj
+Gaim::Cmd::Flag T_IV
+Gaim::Cmd::Id T_IV
+Gaim::Cmd::Priority T_IV
+Gaim::Cmd::Ret T_IV
+Gaim::Connection T_GaimObj
+Gaim::Connection::UiOps T_GaimObj
+Gaim::Conversation T_GaimObj
+Gaim::Conversation::Chat T_GaimObj
+Gaim::Conversation::ChatBuddy T_GaimObj
+Gaim::Conversation::IM T_GaimObj
+Gaim::Conversation::UiOps T_GaimObj
+Gaim::Core T_GaimObj
+Gaim::Core::UiOps T_GaimObj
+
+Gaim::Desktop::Item T_GaimObj
+Gaim::DesktopItemType T_IV
+
+Gaim::Handle T_GaimObj
+
+Gaim::IconScaleRules T_IV
+
+Gaim::Log T_GaimObj
+Gaim::LogType T_IV
+Gaim::Log::CommonLoggerData T_GaimObj
+Gaim::Log::Logger T_GaimObj
+Gaim::Log::ReadFlags T_GaimObj
+Gaim::Log::Set T_GaimObj
+
+Gaim::Menu::Action T_GaimObj
+
+Gaim::NetworkListenData T_GaimObj
+Gaim::NetworkListenCallback T_PTR
+
+Gaim::NotifyCloseCallback T_PTR
+Gaim::NotifyMsgType T_IV
+Gaim::NotifySearchButtonType T_IV
+Gaim::NotifySearchResults T_GaimObj
+Gaim::NotifySearchColumn T_GaimObj
+Gaim::NotifySearchButton T_GaimObj
+Gaim::NotifyType T_IV
+Gaim::NotifyUiOps T_GaimObj
+Gaim::NotifyUserInfo T_GaimObj
+Gaim::NotifyUserInfoEntry T_GaimObj
+
+Gaim::Plugin T_GaimObj
+Gaim::PluginType T_IV
+Gaim::PluginUiInfo T_GaimObj
+Gaim::Plugin::Action T_GaimObj
+Gaim::Plugin::Info T_GaimObj
+Gaim::Plugin::Loader::Info T_GaimObj
+Gaim::Plugin::Protocol::Info T_GaimObj
+Gaim::PrefType T_IV
+Gaim::PluginPref T_GaimObj
+Gaim::PluginPrefType T_IV
+Gaim::PluginPref::Frame T_GaimObj
+Gaim::Pounce T_GaimObj
+Gaim::PounceEvent T_IV
+Gaim::Presence T_GaimObj
+Gaim::PrivacyType T_IV
+Gaim::Privacy::UiOps T_GaimObj
+Gaim::ProtocolOptions T_IV
+Gaim::ProxyInfo T_GaimObj
+Gaim::ProxyType T_IV
+
+Gaim::RequestFieldType T_IV
+Gaim::RequestType T_IV
+Gaim::Request::Field T_GaimObj
+Gaim::Request::Fields T_GaimObj
+Gaim::Request::Field::Group T_GaimObj
+Gaim::Request::UiOps T_GaimObj
+
+Gaim::Roomlist T_GaimObj
+Gaim::Roomlist::Room T_GaimObj
+Gaim::Roomlist::Field T_GaimObj
+Gaim::Roomlist::UiOps T_GaimObj
+Gaim::RoomlistFieldType T_IV
+Gaim::RoomlistRoomType T_IV
+
+Gaim::SavedStatus T_GaimObj
+Gaim::SavedStatusSub T_GaimObj
+Gaim::SoundEventID T_IV
+Gaim::Sound::UiOps T_GaimObj
+
+Gaim::Input::Condition T_GaimObj
+Gaim::SslErrorType T_IV
+Gaim::Ssl::Connection T_GaimObj
+Gaim::Ssl::Ops T_GaimObj
+
+Gaim::Presence T_GaimObj
+Gaim::PresenceContext T_IV
+Gaim::Status T_GaimObj
+Gaim::StatusAttr T_GaimObj
+Gaim::StatusPrimitive T_IV
+Gaim::StatusType T_GaimObj
+const Gaim::StatusType T_GaimObj
+
+Gaim::StoredImage T_GaimObj
+Gaim::Stringref T_GaimObj
+Gaim::Util::FetchUrlData T_PTR
+Gaim::Util::InfoFieldFormatCallback T_PTR
+Gaim::Value T_GaimObj
+
+Gaim::Xfer T_GaimObj
+Gaim::XferType T_IV
+Gaim::XferStatusType T_IV
+Gaim::XferUiOps T_IV
+
+Gaim::XMLNode T_GaimObj
+XMLNode::Type T_IV
+
+/* enums */
+
+/* cipher.h */
+
+/* blist.h */
+
+/* debug.h */
+Gaim::DebugLevel T_IV
+
+/* conversation.h */
+Gaim::ConvChatBuddyFlags T_IV
+Gaim::ConvUpdateType T_IV
+Gaim::ConversationType T_IV
+Gaim::MessageFlags T_IV
+Gaim::TypingState T_IV
+Gaim::UnseenState T_IV
+
+/* connection.h */
+Gaim::ConnectionFlags T_IV
+Gaim::ConnectionState T_IV
+
+INPUT
+
+T_GaimObj
+ $var = gaim_perl_ref_object($arg)
+
+OUTPUT
+
+T_GaimObj
+ $arg = gaim_perl_bless_object($var, \"$type\");
+
+T_GCHAR_OWN
+ /* used when we can directly own the returned string. */
+ /* we have to copy in the case when perl's malloc != gtk's malloc,
+ * so best copy all the time. */
+ sv_setpv ((SV*)$arg, $var);
+ SvUTF8_on ($arg);
+ g_free ($var);
+
diff --git a/libpurple/plugins/perl/libgaimperl.c b/libpurple/plugins/perl/libgaimperl.c
new file mode 100644
index 0000000000..e0c25c3a66
--- /dev/null
+++ b/libpurple/plugins/perl/libgaimperl.c
@@ -0,0 +1,9 @@
+#include <gmodule.h>
+void __attribute__ ((constructor)) my_init(void);
+
+void __attribute__ ((constructor)) my_init() {
+ /* Very evil hack...puts perl.so's symbols in the global table
+ * but does not create a circular dependancy because g_module_open
+ * will only open the library once. */
+ g_module_open("perl.so", 0);
+}
diff --git a/libpurple/plugins/perl/perl-common.c b/libpurple/plugins/perl/perl-common.c
new file mode 100644
index 0000000000..b4daee5f92
--- /dev/null
+++ b/libpurple/plugins/perl/perl-common.c
@@ -0,0 +1,618 @@
+#include "debug.h"
+#include "value.h"
+
+#include "perl-common.h"
+
+extern PerlInterpreter *my_perl;
+
+static GHashTable *object_stashes = NULL;
+
+void gaim_perl_normalize_script_name(char *name)
+{
+ char *c;
+
+ c = strrchr(name, '.');
+
+ if (c != NULL)
+ *c = '\0';
+
+ for (c = name; *c != '\0'; c++) {
+ if (*c != '_' && !g_ascii_isalnum(*c))
+ *c = '_';
+ }
+}
+
+static int
+magic_free_object(pTHX_ SV *sv, MAGIC *mg)
+{
+ sv_setiv(sv, 0);
+
+ return 0;
+}
+
+static MGVTBL vtbl_free_object =
+{
+ NULL, NULL, NULL, NULL, magic_free_object, NULL, NULL
+};
+
+static SV *
+create_sv_ptr(void *object)
+{
+ SV *sv;
+
+ sv = newSViv((IV)object);
+
+ sv_magic(sv, NULL, '~', NULL, 0);
+
+ SvMAGIC(sv)->mg_private = 0x1551; /* HF */
+ SvMAGIC(sv)->mg_virtual = &vtbl_free_object;
+
+ return sv;
+}
+
+SV *
+newSVGChar(const char *str)
+{
+ SV *sv;
+
+ if (str == NULL)
+ return &PL_sv_undef;
+
+ sv = newSVpv(str, 0);
+ SvUTF8_on(sv);
+
+ return sv;
+}
+
+SV *
+gaim_perl_bless_object(void *object, const char *stash_name)
+{
+ HV *stash;
+ HV *hv;
+
+ if (object == NULL)
+ return NULL;
+
+ if (object_stashes == NULL) {
+ object_stashes = g_hash_table_new(g_direct_hash, g_direct_equal);
+ }
+
+ stash = gv_stashpv(stash_name, 1);
+
+ hv = newHV();
+ hv_store(hv, "_gaim", 5, create_sv_ptr(object), 0);
+
+ return sv_bless(newRV_noinc((SV *)hv), stash);
+}
+
+gboolean
+gaim_perl_is_ref_object(SV *o)
+{
+ SV **sv;
+ HV *hv;
+
+ hv = hvref(o);
+
+ if (hv != NULL) {
+ sv = hv_fetch(hv, "_gaim", 5, 0);
+
+ if (sv != NULL)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+void *
+gaim_perl_ref_object(SV *o)
+{
+ SV **sv;
+ HV *hv;
+ void *p;
+
+ if (o == NULL)
+ return NULL;
+
+ hv = hvref(o);
+
+ if (hv == NULL)
+ return NULL;
+
+ sv = hv_fetch(hv, "_gaim", 5, 0);
+
+ if (sv == NULL)
+ croak("variable is damaged");
+
+ p = GINT_TO_POINTER(SvIV(*sv));
+
+ return p;
+}
+
+/*
+ 2003/02/06: execute_perl modified by Mark Doliner <mark@kingant.net>
+ Pass parameters by pushing them onto the stack rather than
+ passing an array of strings. This way, perl scripts can
+ modify the parameters and we can get the changed values
+ and then shoot ourselves. I mean, uh, use them.
+
+ 2001/06/14: execute_perl replaced by Martin Persson <mep@passagen.se>
+ previous use of perl_eval leaked memory, replaced with
+ a version that uses perl_call instead
+
+ 30/11/2002: execute_perl modified by Eric Timme <timothy@voidnet.com>
+ args changed to char** so that we can have preparsed
+ arguments again, and many headaches ensued! This essentially
+ means we replaced one hacked method with a messier hacked
+ method out of perceived necessity. Formerly execute_perl
+ required a single char_ptr, and it would insert it into an
+ array of character pointers and NULL terminate the new array.
+ Now we have to pass in pre-terminated character pointer arrays
+ to accomodate functions that want to pass in multiple arguments.
+
+ Previously arguments were preparsed because an argument list
+ was constructed in the form 'arg one','arg two' and was
+ executed via a call like &funcname(arglist) (see .59.x), so
+ the arglist was magically pre-parsed because of the method.
+ With Martin Persson's change to perl_call we now need to
+ use a null terminated list of character pointers for arguments
+ if we wish them to be parsed. Lacking a better way to allow
+ for both single arguments and many I created a NULL terminated
+ array in every function that called execute_perl and passed
+ that list into the function. In the former version a single
+ character pointer was passed in, and was placed into an array
+ of character pointers with two elements, with a NULL element
+ tacked onto the back, but this method no longer seemed prudent.
+
+ Enhancements in the future might be to get rid of pre-declaring
+ the array sizes? I am not comfortable enough with this
+ subject to attempt it myself and hope it to stand the test
+ of time.
+*/
+int
+execute_perl(const char *function, int argc, char **args)
+{
+ int count = 0, i, ret_value = 1;
+ SV *sv_args[argc];
+ STRLEN na;
+ dSP;
+ PERL_SET_CONTEXT(my_perl);
+ /*
+ * Set up the perl environment, push arguments onto the
+ * perl stack, then call the given function
+ */
+ SPAGAIN;
+ ENTER;
+ SAVETMPS;
+ PUSHMARK(sp);
+
+ for (i = 0; i < argc; i++) {
+ if (args[i]) {
+ sv_args[i] = sv_2mortal(newSVpv(args[i], 0));
+ XPUSHs(sv_args[i]);
+ }
+ }
+
+ PUTBACK;
+ PERL_SET_CONTEXT(my_perl);
+ count = call_pv(function, G_EVAL | G_SCALAR);
+ SPAGAIN;
+
+ /*
+ * Check for "die," make sure we have 1 argument, and set our
+ * return value.
+ */
+ if (SvTRUE(ERRSV)) {
+ gaim_debug(GAIM_DEBUG_ERROR, "perl",
+ "Perl function %s exited abnormally: %s\n",
+ function, SvPV(ERRSV, na));
+ POPs;
+ } else if (count != 1) {
+ /*
+ * This should NEVER happen. G_SCALAR ensures that we WILL
+ * have 1 parameter.
+ */
+ gaim_debug(GAIM_DEBUG_ERROR, "perl",
+ "Perl error from %s: expected 1 return value, "
+ "but got %d\n", function, count);
+ } else
+ ret_value = POPi;
+
+ /* Check for changed arguments */
+ for (i = 0; i < argc; i++) {
+ if (args[i] && strcmp(args[i], SvPVX(sv_args[i]))) {
+ /*
+ * Shizzel. So the perl script changed one of the parameters,
+ * and we want this change to affect the original parameters.
+ * args[i] is just a temporary little list of pointers. We don't
+ * want to free args[i] here because the new parameter doesn't
+ * overwrite the data that args[i] points to. That is done by
+ * the function that called execute_perl. I'm not explaining this
+ * very well. See, it's aggregate... Oh, but if 2 perl scripts
+ * both modify the data, _that's_ a memleak. This is really kind
+ * of hackish. I should fix it. Look how long this comment is.
+ * Holy crap.
+ */
+ args[i] = g_strdup(SvPV(sv_args[i], na));
+ }
+ }
+
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+
+ return ret_value;
+}
+
+#if 0
+gboolean
+gaim_perl_value_from_sv(GaimValue *value, SV *sv)
+{
+ switch (gaim_value_get_type(value))
+ {
+ case GAIM_TYPE_CHAR:
+ if ((tmp = SvGChar(sv)) != NULL)
+ gaim_value_set_char(value, tmp[0]);
+ else
+ return FALSE;
+ break;
+
+ case GAIM_TYPE_UCHAR:
+ if ((tmp = SvPV_nolen(sv)) != NULL)
+ gaim_value_set_uchar(value, tmp[0]);
+ else
+ return FALSE;
+ break;
+
+ case GAIM_TYPE_BOOLEAN:
+ gaim_value_set_boolean(value, SvTRUE(sv));
+ break;
+
+ case GAIM_TYPE_INT:
+ gaim_value_set_int(value, SvIV(sv));
+ break;
+
+ case GAIM_TYPE_UINT:
+ gaim_value_set_uint(value, SvIV(sv));
+ break;
+
+ case GAIM_TYPE_LONG:
+ gaim_value_set_long(value, SvIV(sv));
+ break;
+
+ case GAIM_TYPE_ULONG:
+ gaim_value_set_ulong(value, SvIV(sv));
+ break;
+
+ case GAIM_TYPE_INT64:
+ gaim_value_set_int64(value, SvIV(sv));
+ break;
+
+ case GAIM_TYPE_UINT64:
+ gaim_value_set_uint64(value, SvIV(sv));
+ break;
+
+ case GAIM_TYPE_STRING:
+ gaim_value_set_string(value, SvGChar(sv));
+ break;
+
+ case GAIM_TYPE_POINTER:
+ gaim_value_set_pointer(value, (void *)SvIV(sv));
+ break;
+
+ case GAIM_TYPE_BOXED:
+ if (!strcmp(gaim_value_get_specific_type(value), "SV"))
+ gaim_value_set_boxed(value, (sv == &PL_sv_undef ? NULL : sv));
+ else
+ gaim_value_set_boxed(value, sv);
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+SV *
+gaim_perl_sv_from_value(const GaimValue *value, va_list list)
+{
+ switch (gaim_value_get_type(value))
+ {
+ case GAIM_TYPE_BOOLEAN:
+ return newSViv(gaim_value_get_boolean(value));
+ break;
+
+ case GAIM_TYPE_INT:
+ return newSViv(gaim_value_get_int(value));
+ break;
+
+ case GAIM_TYPE_UINT:
+ return newSVuv(gaim_value_get_uint(value));
+ break;
+
+ case GAIM_TYPE_LONG:
+ return newSViv(gaim_value_get_long(value));
+ break;
+
+ case GAIM_TYPE_ULONG:
+ return newSVuv(gaim_value_get_ulong(value));
+ break;
+
+ case GAIM_TYPE_INT64:
+ return newSViv(gaim_value_get_int64(value));
+ break;
+
+ case GAIM_TYPE_UINT64:
+ return newSVuv(gaim_value_get_int64(value));
+ break;
+
+ case GAIM_TYPE_STRING:
+ return newSVGChar(gaim_value_get_string(value));
+ break;
+
+ case GAIM_TYPE_POINTER:
+ return newSViv((IV)gaim_value_get_pointer(value));
+ break;
+
+ case GAIM_TYPE_BOXED:
+ if (!strcmp(gaim_value_get_specific_type(value), "SV"))
+ {
+ SV *sv = (SV *)gaim_perl_get_boxed(value);
+
+ return (sv == NULL ? &PL_sv_undef : sv);
+ }
+
+ /* Uh.. I dunno. Try this? */
+ return sv_2mortal(gaim_perl_bless_object(
+ gaim_perl_get_boxed(value),
+ gaim_value_get_specific_type(value)));
+
+ default:
+ return FALSE;
+ }
+
+ return TRUE;
+}
+#endif
+
+void *
+gaim_perl_data_from_sv(GaimValue *value, SV *sv)
+{
+ STRLEN na;
+
+ switch (gaim_value_get_type(value)) {
+ case GAIM_TYPE_BOOLEAN: return (void *)SvIV(sv);
+ case GAIM_TYPE_INT: return (void *)SvIV(sv);
+ case GAIM_TYPE_UINT: return (void *)SvUV(sv);
+ case GAIM_TYPE_LONG: return (void *)SvIV(sv);
+ case GAIM_TYPE_ULONG: return (void *)SvUV(sv);
+ case GAIM_TYPE_INT64: return (void *)SvIV(sv);
+ case GAIM_TYPE_UINT64: return (void *)SvUV(sv);
+ case GAIM_TYPE_STRING: return g_strdup((void *)SvPV(sv, na));
+ case GAIM_TYPE_POINTER: return (void *)SvIV(sv);
+ case GAIM_TYPE_BOXED: return (void *)SvIV(sv);
+
+ default:
+ return NULL;
+ }
+
+ return NULL;
+}
+
+static SV *
+gaim_perl_sv_from_subtype(const GaimValue *value, void *arg)
+{
+ const char *stash = NULL;
+
+ switch (gaim_value_get_subtype(value)) {
+ case GAIM_SUBTYPE_ACCOUNT:
+ stash = "Gaim::Account";
+ break;
+ case GAIM_SUBTYPE_BLIST:
+ stash = "Gaim::BuddyList";
+ break;
+ case GAIM_SUBTYPE_BLIST_BUDDY:
+ stash = "Gaim::BuddyList::Buddy";
+ break;
+ case GAIM_SUBTYPE_BLIST_GROUP:
+ stash = "Gaim::BuddyList::Group";
+ break;
+ case GAIM_SUBTYPE_BLIST_CHAT:
+ stash = "Gaim::BuddyList::Chat";
+ break;
+ case GAIM_SUBTYPE_BUDDY_ICON:
+ stash = "Gaim::Buddy::Icon";
+ break;
+ case GAIM_SUBTYPE_CONNECTION:
+ stash = "Gaim::Connection";
+ break;
+ case GAIM_SUBTYPE_CONVERSATION:
+ stash = "Gaim::Conversation";
+ break;
+ case GAIM_SUBTYPE_PLUGIN:
+ stash = "Gaim::Plugin";
+ break;
+ case GAIM_SUBTYPE_BLIST_NODE:
+ stash = "Gaim::BuddyList::Node";
+ break;
+ case GAIM_SUBTYPE_CIPHER:
+ stash = "Gaim::Cipher";
+ break;
+ case GAIM_SUBTYPE_STATUS:
+ stash = "Gaim::Status";
+ break;
+ case GAIM_SUBTYPE_LOG:
+ stash = "Gaim::Log";
+ break;
+ case GAIM_SUBTYPE_XFER:
+ stash = "Gaim::Xfer";
+ break;
+ case GAIM_SUBTYPE_XMLNODE:
+ stash = "Gaim::XMLNode";
+ break;
+
+ default:
+ stash = "Gaim"; /* ? */
+ }
+
+ return sv_2mortal(gaim_perl_bless_object(arg, stash));
+}
+
+SV *
+gaim_perl_sv_from_vargs(const GaimValue *value, va_list *args, void ***copy_arg)
+{
+ if (gaim_value_is_outgoing(value)) {
+ switch (gaim_value_get_type(value)) {
+ case GAIM_TYPE_SUBTYPE:
+ if ((*copy_arg = va_arg(*args, void **)) == NULL)
+ return &PL_sv_undef;
+
+ return gaim_perl_sv_from_subtype(value, *(void **)*copy_arg);
+
+ case GAIM_TYPE_BOOLEAN:
+ if ((*copy_arg = (void *)va_arg(*args, gboolean *)) == NULL)
+ return &PL_sv_undef;
+
+ return newSViv(*(gboolean *)*copy_arg);
+
+ case GAIM_TYPE_INT:
+ if ((*copy_arg = (void *)va_arg(*args, int *)) == NULL)
+ return &PL_sv_undef;
+
+ return newSViv(*(int *)*copy_arg);
+
+ case GAIM_TYPE_UINT:
+ if ((*copy_arg = (void *)va_arg(*args, unsigned int *)) == NULL)
+ return &PL_sv_undef;
+
+ return newSVuv(*(unsigned int *)*copy_arg);
+
+ case GAIM_TYPE_LONG:
+ if ((*copy_arg = (void *)va_arg(*args, long *)) == NULL)
+ return &PL_sv_undef;
+
+ return newSViv(*(long *)*copy_arg);
+
+ case GAIM_TYPE_ULONG:
+ if ((*copy_arg = (void *)va_arg(*args,
+ unsigned long *)) == NULL)
+ return &PL_sv_undef;
+
+ return newSVuv(*(unsigned long *)*copy_arg);
+
+ case GAIM_TYPE_INT64:
+ if ((*copy_arg = (void *)va_arg(*args, gint64 *)) == NULL)
+ return &PL_sv_undef;
+
+ return newSViv(*(gint64 *)*copy_arg);
+
+ case GAIM_TYPE_UINT64:
+ if ((*copy_arg = (void *)va_arg(*args, guint64 *)) == NULL)
+ return &PL_sv_undef;
+
+ return newSVuv(*(guint64 *)*copy_arg);
+
+ case GAIM_TYPE_STRING:
+ if ((*copy_arg = (void *)va_arg(*args, char **)) == NULL)
+ return &PL_sv_undef;
+
+ return newSVGChar(*(char **)*copy_arg);
+
+ case GAIM_TYPE_POINTER:
+ if ((*copy_arg = va_arg(*args, void **)) == NULL)
+ return &PL_sv_undef;
+
+ return newSViv((IV)*(void **)*copy_arg);
+
+ case GAIM_TYPE_BOXED:
+ /* Uh.. I dunno. Try this? */
+ if ((*copy_arg = va_arg(*args, void **)) == NULL)
+ return &PL_sv_undef;
+
+ return sv_2mortal(gaim_perl_bless_object(
+ *(void **)*copy_arg,
+ gaim_value_get_specific_type(value)));
+
+ default:
+ /* If this happens, things are going to get screwed up... */
+ return NULL;
+ }
+ } else {
+ switch (gaim_value_get_type(value)) {
+ case GAIM_TYPE_SUBTYPE:
+ if ((*copy_arg = va_arg(*args, void *)) == NULL)
+ return &PL_sv_undef;
+
+ return gaim_perl_sv_from_subtype(value, *copy_arg);
+
+ case GAIM_TYPE_BOOLEAN:
+ *copy_arg = GINT_TO_POINTER( va_arg(*args, gboolean) );
+
+ return newSViv((gboolean)GPOINTER_TO_INT(*copy_arg));
+
+ case GAIM_TYPE_INT:
+ *copy_arg = GINT_TO_POINTER( va_arg(*args, int) );
+
+ return newSViv(GPOINTER_TO_INT(*copy_arg));
+
+ case GAIM_TYPE_UINT:
+ *copy_arg = GUINT_TO_POINTER(va_arg(*args, unsigned int));
+
+ return newSVuv(GPOINTER_TO_UINT(*copy_arg));
+
+ case GAIM_TYPE_LONG:
+ *copy_arg = (void *)va_arg(*args, long);
+
+ return newSViv((long)*copy_arg);
+
+ case GAIM_TYPE_ULONG:
+ *copy_arg = (void *)va_arg(*args, unsigned long);
+
+ return newSVuv((unsigned long)*copy_arg);
+
+ case GAIM_TYPE_INT64:
+#if 0
+ /* XXX This yells and complains. */
+ *copy_arg = va_arg(*args, gint64);
+
+ return newSViv(*copy_arg);
+#endif
+ break;
+
+ case GAIM_TYPE_UINT64:
+ /* XXX This also yells and complains. */
+#if 0
+ *copy_arg = (void *)va_arg(*args, guint64);
+
+ return newSVuv(*copy_arg);
+#endif
+ break;
+
+ case GAIM_TYPE_STRING:
+ if ((*copy_arg = (void *)va_arg(*args, char *)) == NULL)
+ return &PL_sv_undef;
+
+ return newSVGChar((char *)*copy_arg);
+
+ case GAIM_TYPE_POINTER:
+ if ((*copy_arg = (void *)va_arg(*args, void *)) == NULL)
+ return &PL_sv_undef;
+
+ return newSViv((IV)*copy_arg);
+
+ case GAIM_TYPE_BOXED:
+ /* Uh.. I dunno. Try this? */
+ if ((*copy_arg = (void *)va_arg(*args, void *)) == NULL)
+ return &PL_sv_undef;
+
+ return sv_2mortal(gaim_perl_bless_object(*copy_arg,
+ gaim_value_get_specific_type(value)));
+
+ default:
+ /* If this happens, things are going to get screwed up... */
+ return NULL;
+ }
+ }
+
+ return NULL;
+}
diff --git a/libpurple/plugins/perl/perl-common.h b/libpurple/plugins/perl/perl-common.h
new file mode 100644
index 0000000000..13ef0fa283
--- /dev/null
+++ b/libpurple/plugins/perl/perl-common.h
@@ -0,0 +1,61 @@
+#ifndef _GAIM_PERL_COMMON_H_
+#define _GAIM_PERL_COMMON_H_
+
+#include <glib.h>
+#ifdef _WIN32
+#undef pipe
+#endif
+#include <XSUB.h>
+#include <EXTERN.h>
+#include <perl.h>
+
+#include "plugin.h"
+#include "value.h"
+
+#define is_hvref(o) \
+ ((o) && SvROK(o) && SvRV(o) && (SvTYPE(SvRV(o)) == SVt_PVHV))
+
+#define hvref(o) \
+ (is_hvref(o) ? (HV *)SvRV(o) : NULL);
+
+#define GAIM_PERL_BOOT_PROTO(x) \
+ void boot_Gaim__##x(pTHX_ CV *cv);
+
+#define GAIM_PERL_BOOT(x) \
+ gaim_perl_callXS(boot_Gaim__##x, cv, mark)
+
+typedef struct
+{
+ GaimPlugin *plugin;
+ char *package;
+ char *load_sub;
+ char *unload_sub;
+ char *prefs_sub;
+#ifdef GAIM_GTKPERL
+ char *gtk_prefs_sub;
+#endif
+ char *plugin_action_sub;
+} GaimPerlScript;
+
+void gaim_perl_normalize_script_name(char *name);
+
+SV *newSVGChar(const char *str);
+
+void gaim_perl_callXS(void (*subaddr)(pTHX_ CV *cv), CV *cv, SV **mark);
+void gaim_perl_bless_plain(const char *stash, void *object);
+SV *gaim_perl_bless_object(void *object, const char *stash);
+gboolean gaim_perl_is_ref_object(SV *o);
+void *gaim_perl_ref_object(SV *o);
+
+int execute_perl(const char *function, int argc, char **args);
+
+#if 0
+gboolean gaim_perl_value_from_sv(GaimValue *value, SV *sv);
+SV *gaim_perl_sv_from_value(const GaimValue *value);
+#endif
+
+void *gaim_perl_data_from_sv(GaimValue *value, SV *sv);
+SV *gaim_perl_sv_from_vargs(const GaimValue *value, va_list *args,
+ void ***copy_arg);
+
+#endif /* _GAIM_PERL_COMMON_H_ */
diff --git a/libpurple/plugins/perl/perl-handlers.c b/libpurple/plugins/perl/perl-handlers.c
new file mode 100644
index 0000000000..1d6b61d4c6
--- /dev/null
+++ b/libpurple/plugins/perl/perl-handlers.c
@@ -0,0 +1,636 @@
+#include "perl-common.h"
+#include "perl-handlers.h"
+
+#include "debug.h"
+#include "signals.h"
+
+extern PerlInterpreter *my_perl;
+static GList *cmd_handlers = NULL;
+static GList *signal_handlers = NULL;
+static GList *timeout_handlers = NULL;
+
+/* perl < 5.8.0 doesn't define PERL_MAGIC_ext */
+#ifndef PERL_MAGIC_ext
+#define PERL_MAGIC_ext '~'
+#endif
+
+void
+gaim_perl_plugin_action_cb(GaimPluginAction *action)
+{
+ SV **callback;
+ HV *hv = NULL;
+ gchar *hvname;
+ GaimPlugin *plugin;
+ GaimPerlScript *gps;
+ dSP;
+
+ plugin = action->plugin;
+ gps = (GaimPerlScript *)plugin->info->extra_info;
+ hvname = g_strdup_printf("%s::plugin_actions", gps->package);
+ hv = get_hv(hvname, FALSE);
+ g_free(hvname);
+
+ if (hv == NULL)
+ croak("No plugin_actions hash found in \"%s\" plugin.", gaim_plugin_get_name(plugin));
+
+ ENTER;
+ SAVETMPS;
+
+ callback = hv_fetch(hv, action->label, strlen(action->label), 0);
+
+ if (callback == NULL || *callback == NULL)
+ croak("No plugin_action function named \"%s\" in \"%s\" plugin.", action->label, gaim_plugin_get_name(plugin));
+
+ PUSHMARK(sp);
+ XPUSHs(gaim_perl_bless_object(gps->plugin, "Gaim::Plugin"));
+ PUTBACK;
+
+ call_sv(*callback, G_VOID | G_DISCARD);
+ SPAGAIN;
+
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+}
+
+GList *
+gaim_perl_plugin_actions(GaimPlugin *plugin, gpointer context)
+{
+ GList *l = NULL;
+ GaimPerlScript *gps;
+ int i = 0, count = 0;
+ dSP;
+
+ gps = (GaimPerlScript *)plugin->info->extra_info;
+
+ ENTER;
+ SAVETMPS;
+
+ PUSHMARK(SP);
+ XPUSHs(sv_2mortal(gaim_perl_bless_object(plugin, "Gaim::Plugin")));
+ /* XXX This *will* cease working correctly if context gets changed to
+ * ever be able to hold anything other than a GaimConnection */
+ if (context != NULL)
+ XPUSHs(sv_2mortal(gaim_perl_bless_object(context,
+ "Gaim::Connection")));
+ else
+ XPUSHs(&PL_sv_undef);
+ PUTBACK;
+
+ count = call_pv(gps->plugin_action_sub, G_ARRAY);
+
+ SPAGAIN;
+
+ if (count == 0)
+ croak("The plugin_actions sub didn't return anything.\n");
+
+ for (i = 0; i < count; i++) {
+ SV *sv;
+ gchar *label;
+ GaimPluginAction *act = NULL;
+
+ sv = POPs;
+ label = SvPV_nolen(sv);
+ /* XXX I think this leaks, but doing it without the strdup
+ * just showed garbage */
+ act = gaim_plugin_action_new(g_strdup(label), gaim_perl_plugin_action_cb);
+ l = g_list_prepend(l, act);
+ }
+
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+
+ return l;
+}
+
+#ifdef GAIM_GTKPERL
+GtkWidget *
+gaim_perl_gtk_get_plugin_frame(GaimPlugin *plugin)
+{
+ SV * sv;
+ int count;
+ MAGIC *mg;
+ GtkWidget *ret;
+ GaimPerlScript *gps;
+ dSP;
+
+ gps = (GaimPerlScript *)plugin->info->extra_info;
+
+ ENTER;
+ SAVETMPS;
+
+ count = call_pv(gps->gtk_prefs_sub, 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;
+
+ /* We have a Gtk2::Frame on top of the stack */
+ sv = POPs;
+
+ /* The magic field hides the pointer to the actual GtkWidget */
+ mg = mg_find(SvRV(sv), PERL_MAGIC_ext);
+ ret = (GtkWidget *)mg->mg_ptr;
+
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+
+ return ret;
+}
+#endif
+
+GaimPluginPrefFrame *
+gaim_perl_get_plugin_frame(GaimPlugin *plugin)
+{
+ /* Sets up the Perl Stack for our call back into the script to run the
+ * plugin_pref... sub */
+ int count;
+ GaimPerlScript *gps;
+ GaimPluginPrefFrame *ret_frame;
+ dSP;
+
+ gps = (GaimPerlScript *)plugin->info->extra_info;
+
+ ENTER;
+ SAVETMPS;
+ /* Some perl magic to run perl_plugin_pref_frame_SV perl sub and
+ * return the frame */
+ PUSHMARK(SP);
+ PUTBACK;
+
+ count = call_pv(gps->prefs_sub, G_SCALAR | G_NOARGS);
+
+ SPAGAIN;
+
+ 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 */
+ ret_frame = (GaimPluginPrefFrame *)gaim_perl_ref_object(POPs);
+
+ /* Tidy up the Perl stack */
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+
+ return ret_frame;
+}
+
+static void
+destroy_timeout_handler(GaimPerlTimeoutHandler *handler)
+{
+ timeout_handlers = g_list_remove(timeout_handlers, handler);
+
+ if (handler->callback != NULL)
+ SvREFCNT_dec(handler->callback);
+
+ if (handler->data != NULL)
+ SvREFCNT_dec(handler->data);
+
+ g_free(handler);
+}
+
+static void
+destroy_signal_handler(GaimPerlSignalHandler *handler)
+{
+ signal_handlers = g_list_remove(signal_handlers, handler);
+
+ if (handler->callback != NULL)
+ SvREFCNT_dec(handler->callback);
+
+ if (handler->data != NULL)
+ SvREFCNT_dec(handler->data);
+
+ g_free(handler->signal);
+ g_free(handler);
+}
+
+static int
+perl_timeout_cb(gpointer data)
+{
+ GaimPerlTimeoutHandler *handler = (GaimPerlTimeoutHandler *)data;
+
+ dSP;
+ ENTER;
+ SAVETMPS;
+ PUSHMARK(sp);
+ XPUSHs((SV *)handler->data);
+ PUTBACK;
+ call_sv(handler->callback, G_EVAL | G_SCALAR);
+ SPAGAIN;
+
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+
+ destroy_timeout_handler(handler);
+
+ return 0;
+}
+
+typedef void *DATATYPE;
+
+static void *
+perl_signal_cb(va_list args, void *data)
+{
+ GaimPerlSignalHandler *handler = (GaimPerlSignalHandler *)data;
+ void *ret_val = NULL;
+ int i;
+ int count;
+ int value_count;
+ GaimValue *ret_value, **values;
+ SV **sv_args;
+ DATATYPE **copy_args;
+ STRLEN na;
+
+ dSP;
+ ENTER;
+ SAVETMPS;
+ PUSHMARK(sp);
+
+ gaim_signal_get_values(handler->instance, handler->signal,
+ &ret_value, &value_count, &values);
+
+ sv_args = g_new(SV *, value_count);
+ copy_args = g_new(void **, value_count);
+
+ for (i = 0; i < value_count; i++) {
+ sv_args[i] = gaim_perl_sv_from_vargs(values[i],
+ (va_list*)&args,
+ &copy_args[i]);
+
+ XPUSHs(sv_args[i]);
+ }
+
+ XPUSHs((SV *)handler->data);
+
+ PUTBACK;
+
+ if (ret_value != NULL) {
+ count = call_sv(handler->callback, G_EVAL | G_SCALAR);
+
+ SPAGAIN;
+
+ if (count != 1)
+ croak("Uh oh! call_sv returned %i != 1", i);
+ else
+ ret_val = gaim_perl_data_from_sv(ret_value, POPs);
+ } else {
+ call_sv(handler->callback, G_SCALAR);
+
+ SPAGAIN;
+ }
+
+ if (SvTRUE(ERRSV)) {
+ gaim_debug_error("perl",
+ "Perl function exited abnormally: %s\n",
+ SvPV(ERRSV, na));
+ }
+
+ /* See if any parameters changed. */
+ for (i = 0; i < value_count; i++) {
+ if (gaim_value_is_outgoing(values[i])) {
+ switch (gaim_value_get_type(values[i])) {
+ case GAIM_TYPE_BOOLEAN:
+ *((gboolean *)copy_args[i]) = SvIV(sv_args[i]);
+ break;
+
+ case GAIM_TYPE_INT:
+ *((int *)copy_args[i]) = SvIV(sv_args[i]);
+ break;
+
+ case GAIM_TYPE_UINT:
+ *((unsigned int *)copy_args[i]) = SvUV(sv_args[i]);
+ break;
+
+ case GAIM_TYPE_LONG:
+ *((long *)copy_args[i]) = SvIV(sv_args[i]);
+ break;
+
+ case GAIM_TYPE_ULONG:
+ *((unsigned long *)copy_args[i]) = SvUV(sv_args[i]);
+ break;
+
+ case GAIM_TYPE_INT64:
+ *((gint64 *)copy_args[i]) = SvIV(sv_args[i]);
+ break;
+
+ case GAIM_TYPE_UINT64:
+ *((guint64 *)copy_args[i]) = SvUV(sv_args[i]);
+ break;
+
+ case GAIM_TYPE_STRING:
+ if (strcmp(*((char **)copy_args[i]), SvPVX(sv_args[i]))) {
+ g_free(*((char **)copy_args[i]));
+ *((char **)copy_args[i]) =
+ g_strdup(SvPV(sv_args[i], na));
+ }
+ break;
+
+ case GAIM_TYPE_POINTER:
+ *((void **)copy_args[i]) = (void *)SvIV(sv_args[i]);
+ break;
+
+ case GAIM_TYPE_BOXED:
+ *((void **)copy_args[i]) = (void *)SvIV(sv_args[i]);
+ break;
+
+ default:
+ break;
+ }
+
+#if 0
+ *((void **)copy_args[i]) = gaim_perl_data_from_sv(values[i],
+ sv_args[i]);
+#endif
+ }
+ }
+
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+
+ g_free(sv_args);
+ g_free(copy_args);
+
+ gaim_debug_misc("perl", "ret_val = %p\n", ret_val);
+
+ return ret_val;
+}
+
+static GaimPerlSignalHandler *
+find_signal_handler(GaimPlugin *plugin, void *instance, const char *signal)
+{
+ GaimPerlSignalHandler *handler;
+ GList *l;
+
+ for (l = signal_handlers; l != NULL; l = l->next) {
+ handler = (GaimPerlSignalHandler *)l->data;
+
+ if (handler->plugin == plugin &&
+ handler->instance == instance &&
+ !strcmp(handler->signal, signal)) {
+ return handler;
+ }
+ }
+
+ return NULL;
+}
+
+void
+gaim_perl_timeout_add(GaimPlugin *plugin, int seconds, SV *callback, SV *data)
+{
+ GaimPerlTimeoutHandler *handler;
+
+ if (plugin == NULL) {
+ croak("Invalid handle in adding perl timeout handler.\n");
+ return;
+ }
+
+ handler = g_new0(GaimPerlTimeoutHandler, 1);
+
+ handler->plugin = plugin;
+ handler->callback = (callback != NULL && callback != &PL_sv_undef
+ ? newSVsv(callback) : NULL);
+ handler->data = (data != NULL && data != &PL_sv_undef
+ ? newSVsv(data) : NULL);
+
+ timeout_handlers = g_list_append(timeout_handlers, handler);
+
+ handler->iotag = g_timeout_add(seconds * 1000, perl_timeout_cb, handler);
+}
+
+void
+gaim_perl_timeout_clear_for_plugin(GaimPlugin *plugin)
+{
+ GaimPerlTimeoutHandler *handler;
+ GList *l, *l_next;
+
+ for (l = timeout_handlers; l != NULL; l = l_next) {
+ l_next = l->next;
+
+ handler = (GaimPerlTimeoutHandler *)l->data;
+
+ if (handler->plugin == plugin)
+ destroy_timeout_handler(handler);
+ }
+}
+
+void
+gaim_perl_timeout_clear(void)
+{
+ while (timeout_handlers != NULL)
+ destroy_timeout_handler(timeout_handlers->data);
+}
+
+void
+gaim_perl_signal_connect(GaimPlugin *plugin, void *instance,
+ const char *signal, SV *callback, SV *data,
+ int priority)
+{
+ GaimPerlSignalHandler *handler;
+
+ handler = g_new0(GaimPerlSignalHandler, 1);
+ handler->plugin = plugin;
+ handler->instance = instance;
+ handler->signal = g_strdup(signal);
+ handler->callback = (callback != NULL &&
+ callback != &PL_sv_undef ? newSVsv(callback)
+ : NULL);
+ handler->data = (data != NULL &&
+ data != &PL_sv_undef ? newSVsv(data) : NULL);
+
+ signal_handlers = g_list_append(signal_handlers, handler);
+
+ gaim_signal_connect_priority_vargs(instance, signal, plugin,
+ GAIM_CALLBACK(perl_signal_cb),
+ handler, priority);
+}
+
+void
+gaim_perl_signal_disconnect(GaimPlugin *plugin, void *instance,
+ const char *signal)
+{
+ GaimPerlSignalHandler *handler;
+
+ handler = find_signal_handler(plugin, instance, signal);
+
+ if (handler == NULL) {
+ croak("Invalid signal handler information in "
+ "disconnecting a perl signal handler.\n");
+ return;
+ }
+
+ destroy_signal_handler(handler);
+}
+
+void
+gaim_perl_signal_clear_for_plugin(GaimPlugin *plugin)
+{
+ GaimPerlSignalHandler *handler;
+ GList *l, *l_next;
+
+ for (l = signal_handlers; l != NULL; l = l_next) {
+ l_next = l->next;
+
+ handler = (GaimPerlSignalHandler *)l->data;
+
+ if (handler->plugin == plugin)
+ destroy_signal_handler(handler);
+ }
+}
+
+void
+gaim_perl_signal_clear(void)
+{
+ while (signal_handlers != NULL)
+ destroy_signal_handler(signal_handlers->data);
+}
+
+static GaimCmdRet
+perl_cmd_cb(GaimConversation *conv, const gchar *command,
+ gchar **args, gchar **error, void *data)
+{
+ int i = 0, count, ret_value = GAIM_CMD_RET_OK;
+ SV *cmdSV, *tmpSV, *convSV;
+ GaimPerlCmdHandler *handler = (GaimPerlCmdHandler *)data;
+
+ dSP;
+ ENTER;
+ SAVETMPS;
+ PUSHMARK(SP);
+
+ /* Push the conversation onto the perl stack */
+ convSV = sv_2mortal(gaim_perl_bless_object(conv, "Gaim::Conversation"));
+ XPUSHs(convSV);
+
+ /* Push the command string onto the perl stack */
+ cmdSV = newSVpv(command, 0);
+ cmdSV = sv_2mortal(cmdSV);
+ XPUSHs(cmdSV);
+
+ /* Push the data onto the perl stack */
+ XPUSHs((SV *)handler->data);
+
+ /* Push any arguments we may have */
+ for (i = 0; args[i] != NULL; i++) {
+ /* XXX The mortality of these created SV's should prevent
+ * memory issues, if I read/understood everything correctly...
+ */
+ tmpSV = newSVpv(args[i], 0);
+ tmpSV = sv_2mortal(tmpSV);
+ XPUSHs(tmpSV);
+ }
+
+ PUTBACK;
+ count = call_sv(handler->callback, G_EVAL|G_SCALAR);
+
+ if (count != 1)
+ croak("call_sv: Did not return the correct number of values.\n");
+
+ SPAGAIN;
+
+ ret_value = POPi;
+
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+
+ return ret_value;
+}
+
+GaimCmdId
+gaim_perl_cmd_register(GaimPlugin *plugin, const gchar *command,
+ const gchar *args, GaimCmdPriority priority,
+ GaimCmdFlag flag, const gchar *prpl_id, SV *callback,
+ const gchar *helpstr, SV *data)
+{
+ GaimPerlCmdHandler *handler;
+
+ handler = g_new0(GaimPerlCmdHandler, 1);
+ handler->plugin = plugin;
+ handler->cmd = g_strdup(command);
+ handler->prpl_id = g_strdup(prpl_id);
+
+ if (callback != NULL && callback != &PL_sv_undef)
+ handler->callback = newSVsv(callback);
+ else
+ handler->callback = NULL;
+
+ if (data != NULL && data != &PL_sv_undef)
+ handler->data = newSVsv(data);
+ else
+ handler->data = NULL;
+
+ cmd_handlers = g_list_append(cmd_handlers, handler);
+
+ handler->id = gaim_cmd_register(command, args, priority, flag, prpl_id,
+ GAIM_CMD_FUNC(perl_cmd_cb), helpstr,
+ handler);
+
+ return handler->id;
+}
+
+static void
+destroy_cmd_handler(GaimPerlCmdHandler *handler)
+{
+ cmd_handlers = g_list_remove(cmd_handlers, handler);
+
+ if (handler->callback != NULL)
+ SvREFCNT_dec(handler->callback);
+
+ if (handler->data != NULL)
+ SvREFCNT_dec(handler->data);
+
+ g_free(handler->cmd);
+ g_free(handler->prpl_id);
+ g_free(handler);
+}
+
+void
+gaim_perl_cmd_clear_for_plugin(GaimPlugin *plugin)
+{
+ GList *l, *l_next;
+
+ for (l = cmd_handlers; l != NULL; l = l_next) {
+ GaimPerlCmdHandler *handler = (GaimPerlCmdHandler *)l->data;
+
+ l_next = l->next;
+
+ if (handler->plugin == plugin)
+ destroy_cmd_handler(handler);
+ }
+}
+
+static GaimPerlCmdHandler *
+find_cmd_handler(GaimCmdId id)
+{
+ GList *l;
+
+ for (l = cmd_handlers; l != NULL; l = l->next) {
+ GaimPerlCmdHandler *handler = (GaimPerlCmdHandler *)l->data;
+
+ if (handler->id == id)
+ return handler;
+ }
+
+ return NULL;
+}
+
+void
+gaim_perl_cmd_unregister(GaimCmdId id)
+{
+ GaimPerlCmdHandler *handler;
+
+ handler = find_cmd_handler(id);
+
+ if (handler == NULL) {
+ croak("Invalid command id in removing a perl command handler.\n");
+ return;
+ }
+
+ gaim_cmd_unregister(id);
+ destroy_cmd_handler(handler);
+}
diff --git a/libpurple/plugins/perl/perl-handlers.h b/libpurple/plugins/perl/perl-handlers.h
new file mode 100644
index 0000000000..9f0232fc68
--- /dev/null
+++ b/libpurple/plugins/perl/perl-handlers.h
@@ -0,0 +1,71 @@
+#ifndef _GAIM_PERL_HANDLERS_H_
+#define _GAIM_PERL_HANDLERS_H_
+
+#include "cmds.h"
+#include "plugin.h"
+#include "prefs.h"
+#include "pluginpref.h"
+#ifdef GAIM_GTKPERL
+#include "gtkplugin.h"
+#include "gtkutils.h"
+#endif
+
+typedef struct
+{
+ GaimCmdId id;
+ SV *callback;
+ SV *data;
+ char *prpl_id;
+ char *cmd;
+ GaimPlugin *plugin;
+} GaimPerlCmdHandler;
+
+typedef struct
+{
+ SV *callback;
+ SV *data;
+ GaimPlugin *plugin;
+ int iotag;
+
+} GaimPerlTimeoutHandler;
+
+typedef struct
+{
+ char *signal;
+ SV *callback;
+ SV *data;
+ void *instance;
+ GaimPlugin *plugin;
+
+} GaimPerlSignalHandler;
+
+void gaim_perl_plugin_action_cb(GaimPluginAction * gpa);
+GList *gaim_perl_plugin_actions(GaimPlugin *plugin, gpointer context);
+
+GaimPluginPrefFrame *gaim_perl_get_plugin_frame(GaimPlugin *plugin);
+
+#ifdef GAIM_GTKPERL
+GtkWidget *gaim_perl_gtk_get_plugin_frame(GaimPlugin *plugin);
+#endif
+
+void gaim_perl_timeout_add(GaimPlugin *plugin, int seconds, SV *callback,
+ SV *data);
+void gaim_perl_timeout_clear_for_plugin(GaimPlugin *plugin);
+void gaim_perl_timeout_clear(void);
+
+void gaim_perl_signal_connect(GaimPlugin *plugin, void *instance,
+ const char *signal, SV *callback,
+ SV *data, int priority);
+void gaim_perl_signal_disconnect(GaimPlugin *plugin, void *instance,
+ const char *signal);
+void gaim_perl_signal_clear_for_plugin(GaimPlugin *plugin);
+void gaim_perl_signal_clear(void);
+
+GaimCmdId gaim_perl_cmd_register(GaimPlugin *plugin, const gchar *cmd,
+ const gchar *args, GaimCmdPriority priority,
+ GaimCmdFlag flag, const gchar *prpl_id,
+ SV *callback, const gchar *helpstr, SV *data);
+void gaim_perl_cmd_unregister(GaimCmdId id);
+void gaim_perl_cmd_clear_for_plugin(GaimPlugin *plugin);
+
+#endif /* _GAIM_PERL_HANDLERS_H_ */
diff --git a/libpurple/plugins/perl/perl.c b/libpurple/plugins/perl/perl.c
new file mode 100644
index 0000000000..45d6e18eda
--- /dev/null
+++ b/libpurple/plugins/perl/perl.c
@@ -0,0 +1,611 @@
+/*
+ * gaim
+ *
+ * Copyright (C) 2003 Christian Hammond <chipx86@gnupdate.org>
+ *
+ * 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
+ */
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+# ifdef HAVE_LIMITS_H
+# include <limits.h>
+# ifndef NAME_MAX
+# define NAME_MAX _POSIX_NAME_MAX
+# endif
+# endif
+#endif
+
+#ifdef DEBUG
+# undef DEBUG
+#endif
+
+#undef PACKAGE
+
+#define group perl_group
+
+#ifdef _WIN32
+/* This took me an age to figure out.. without this __declspec(dllimport)
+ * will be ignored.
+ */
+# define HASATTRIBUTE
+#endif
+
+#include <EXTERN.h>
+
+#ifndef _SEM_SEMUN_UNDEFINED
+# define HAS_UNION_SEMUN
+#endif
+
+#include <perl.h>
+#include <XSUB.h>
+
+#ifndef _WIN32
+# include <sys/mman.h>
+#endif
+
+#undef PACKAGE
+
+#ifndef _WIN32
+# include <dirent.h>
+#else
+ /* We're using perl's win32 port of this */
+# define dirent direct
+#endif
+
+#undef group
+
+/* perl module support */
+#ifdef OLD_PERL
+extern void boot_DynaLoader _((CV * cv));
+#else
+extern void boot_DynaLoader _((pTHX_ CV * cv)); /* perl is so wacky */
+#endif
+
+#undef _
+#ifdef DEBUG
+# undef DEBUG
+#endif
+#ifdef _WIN32
+# undef pipe
+#endif
+
+#ifdef _WIN32
+#define _WIN32DEP_H_
+#endif
+#include "internal.h"
+#include "debug.h"
+#include "plugin.h"
+#include "signals.h"
+#include "version.h"
+
+#include "perl-common.h"
+#include "perl-handlers.h"
+
+#define PERL_PLUGIN_ID "core-perl"
+
+PerlInterpreter *my_perl = NULL;
+
+static GaimPluginUiInfo ui_info =
+{
+ gaim_perl_get_plugin_frame,
+ 0, /* page_num (Reserved) */
+ NULL /* frame (Reserved) */
+};
+
+#ifdef GAIM_GTKPERL
+static GaimGtkPluginUiInfo gtk_ui_info =
+{
+ gaim_perl_gtk_get_plugin_frame,
+ 0 /* page_num (Reserved) */
+};
+#endif
+
+static void
+#ifdef OLD_PERL
+xs_init()
+#else
+xs_init(pTHX)
+#endif
+{
+ char *file = __FILE__;
+
+ /* This one allows dynamic loading of perl modules in perl scripts by
+ * the 'use perlmod;' construction */
+ newXS("DynaLoader::boot_DynaLoader", boot_DynaLoader, file);
+}
+
+static void
+perl_init(void)
+{
+ /* changed the name of the variable from load_file to perl_definitions
+ * since now it does much more than defining the load_file sub.
+ * Moreover, deplaced the initialisation to the xs_init function.
+ * (TheHobbit) */
+ char *perl_args[] = { "", "-e", "0", "-w" };
+ char perl_definitions[] =
+ {
+ /* We use to function one to load a file the other to execute
+ * the string obtained from the first and holding the file
+ * contents. This allows to have a really local $/ without
+ * introducing temp variables to hold the old value. Just a
+ * question of style:) */
+ "package Gaim::PerlLoader;"
+ "use Symbol;"
+
+ "sub load_file {"
+ "my $f_name=shift;"
+ "local $/=undef;"
+ "open FH,$f_name or return \"__FAILED__\";"
+ "$_=<FH>;"
+ "close FH;"
+ "return $_;"
+ "}"
+
+ "sub destroy_package {"
+ "eval { $_[0]->UNLOAD() if $_[0]->can('UNLOAD'); };"
+ "Symbol::delete_package($_[0]);"
+ "}"
+
+ "sub load_n_eval {"
+ "my ($f_name, $package) = @_;"
+ "destroy_package($package);"
+ "my $strin=load_file($f_name);"
+ "return 2 if($strin eq \"__FAILED__\");"
+ "my $eval = qq{package $package; $strin;};"
+
+ "{"
+ " eval $eval;"
+ "}"
+
+ "if($@) {"
+ /*" #something went wrong\n"*/
+ "die(\"Errors loading file $f_name: $@\");"
+ "}"
+
+ "return 0;"
+ "}"
+ };
+
+ my_perl = perl_alloc();
+ PERL_SET_CONTEXT(my_perl);
+ PL_perl_destruct_level = 1;
+ perl_construct(my_perl);
+#ifdef DEBUG
+ perl_parse(my_perl, xs_init, 4, perl_args, NULL);
+#else
+ perl_parse(my_perl, xs_init, 3, perl_args, NULL);
+#endif
+#ifdef HAVE_PERL_EVAL_PV
+ eval_pv(perl_definitions, TRUE);
+#else
+ perl_eval_pv(perl_definitions, TRUE); /* deprecated */
+#endif
+ perl_run(my_perl);
+}
+
+static void
+perl_end(void)
+{
+ if (my_perl == NULL)
+ return;
+
+ PL_perl_destruct_level = 1;
+ PERL_SET_CONTEXT(my_perl);
+ perl_eval_pv(
+ "foreach my $lib (@DynaLoader::dl_modules) {"
+ "if ($lib =~ /^Gaim\\b/) {"
+ "$lib .= '::deinit();';"
+ "eval $lib;"
+ "}"
+ "}",
+ TRUE);
+
+ PL_perl_destruct_level = 1;
+ PERL_SET_CONTEXT(my_perl);
+ perl_destruct(my_perl);
+ perl_free(my_perl);
+ my_perl = NULL;
+}
+
+void
+gaim_perl_callXS(void (*subaddr)(pTHX_ CV *cv), CV *cv, SV **mark)
+{
+ dSP;
+
+ PUSHMARK(mark);
+ (*subaddr)(aTHX_ cv);
+
+ PUTBACK;
+}
+
+static gboolean
+probe_perl_plugin(GaimPlugin *plugin)
+{
+ /* XXX This would be much faster if I didn't create a new
+ * PerlInterpreter every time I probed a plugin */
+
+ PerlInterpreter *prober = perl_alloc();
+ char *argv[] = {"", plugin->path };
+ gboolean status = TRUE;
+ HV *plugin_info;
+ PERL_SET_CONTEXT(prober);
+ PL_perl_destruct_level = 1;
+ perl_construct(prober);
+
+ perl_parse(prober, xs_init, 2, argv, NULL);
+
+ perl_run(prober);
+
+ plugin_info = perl_get_hv("PLUGIN_INFO", FALSE);
+
+ if (plugin_info == NULL)
+ status = FALSE;
+ else if (!hv_exists(plugin_info, "perl_api_version",
+ strlen("perl_api_version")) ||
+ !hv_exists(plugin_info, "name", strlen("name")) ||
+ !hv_exists(plugin_info, "load", strlen("load"))) {
+ /* Not a valid plugin. */
+
+ status = FALSE;
+ } else {
+ SV **key;
+ int perl_api_ver;
+
+ key = hv_fetch(plugin_info, "perl_api_version",
+ strlen("perl_api_version"), 0);
+
+ perl_api_ver = SvIV(*key);
+
+ if (perl_api_ver != 2)
+ status = FALSE;
+ else {
+ GaimPluginInfo *info;
+ GaimPerlScript *gps;
+ char *basename;
+ STRLEN len;
+
+ info = g_new0(GaimPluginInfo, 1);
+ gps = g_new0(GaimPerlScript, 1);
+
+ info->magic = GAIM_PLUGIN_MAGIC;
+ info->major_version = GAIM_MAJOR_VERSION;
+ info->minor_version = GAIM_MINOR_VERSION;
+ info->type = GAIM_PLUGIN_STANDARD;
+
+ info->dependencies = g_list_append(info->dependencies,
+ PERL_PLUGIN_ID);
+
+ gps->plugin = plugin;
+
+ basename = g_path_get_basename(plugin->path);
+ gaim_perl_normalize_script_name(basename);
+ gps->package = g_strdup_printf("Gaim::Script::%s",
+ basename);
+ g_free(basename);
+
+ /* We know this one exists. */
+ key = hv_fetch(plugin_info, "name", strlen("name"), 0);
+ info->name = g_strdup(SvPV(*key, len));
+ /* Set id here in case we don't find one later. */
+ info->id = g_strdup(SvPV(*key, len));
+
+#ifdef GAIM_GTKPERL
+ if ((key = hv_fetch(plugin_info, "GTK_UI",
+ strlen("GTK_UI"), 0)))
+ info->ui_requirement = GAIM_GTK_PLUGIN_TYPE;
+#endif
+
+ if ((key = hv_fetch(plugin_info, "url",
+ strlen("url"), 0)))
+ info->homepage = g_strdup(SvPV(*key, len));
+
+ if ((key = hv_fetch(plugin_info, "author",
+ strlen("author"), 0)))
+ info->author = g_strdup(SvPV(*key, len));
+
+ if ((key = hv_fetch(plugin_info, "summary",
+ strlen("summary"), 0)))
+ info->summary = g_strdup(SvPV(*key, len));
+
+ if ((key = hv_fetch(plugin_info, "description",
+ strlen("description"), 0)))
+ info->description = g_strdup(SvPV(*key, len));
+
+ if ((key = hv_fetch(plugin_info, "version",
+ strlen("version"), 0)))
+ info->version = g_strdup(SvPV(*key, len));
+
+ /* We know this one exists. */
+ key = hv_fetch(plugin_info, "load", strlen("load"), 0);
+ gps->load_sub = g_strdup_printf("%s::%s", gps->package,
+ SvPV(*key, len));
+
+ if ((key = hv_fetch(plugin_info, "unload",
+ strlen("unload"), 0)))
+ gps->unload_sub = g_strdup_printf("%s::%s",
+ gps->package,
+ SvPV(*key, len));
+
+ if ((key = hv_fetch(plugin_info, "id",
+ strlen("id"), 0))) {
+ g_free(info->id);
+ info->id = g_strdup_printf("perl-%s",
+ SvPV(*key, len));
+ }
+
+ /********************************************************/
+ /* Only one of the next two options should be present */
+ /* */
+ /* prefs_info - Uses non-GUI (read GTK) gaim API calls */
+ /* and creates a GaimPluginPrefInfo type. */
+ /* */
+ /* gtk_prefs_info - Requires gtk2-perl be installed by */
+ /* the user and he must create a */
+ /* GtkWidget the user and he must */
+ /* create a GtkWidget representing the */
+ /* plugin preferences page. */
+ /********************************************************/
+ if ((key = hv_fetch(plugin_info, "prefs_info",
+ strlen("prefs_info"), 0))) {
+ /* key now is the name of the Perl sub that
+ * will create a frame for us */
+ gps->prefs_sub = g_strdup_printf("%s::%s",
+ gps->package,
+ SvPV(*key, len));
+ info->prefs_info = &ui_info;
+ }
+
+#ifdef GAIM_GTKPERL
+ if ((key = hv_fetch(plugin_info, "gtk_prefs_info",
+ strlen("gtk_prefs_info"), 0))) {
+ /* key now is the name of the Perl sub that
+ * will create a frame for us */
+ gps->gtk_prefs_sub = g_strdup_printf("%s::%s",
+ gps->package,
+ SvPV(*key, len));
+ info->ui_info = &gtk_ui_info;
+ }
+#endif
+
+ if ((key = hv_fetch(plugin_info, "plugin_action_sub",
+ strlen("plugin_action_sub"), 0))) {
+ gps->plugin_action_sub = g_strdup_printf("%s::%s",
+ gps->package,
+ SvPV(*key, len));
+ info->actions = gaim_perl_plugin_actions;
+ }
+
+ plugin->info = info;
+ info->extra_info = gps;
+
+ status = gaim_plugin_register(plugin);
+ }
+ }
+
+ PL_perl_destruct_level = 1;
+ PERL_SET_CONTEXT(prober);
+ perl_destruct(prober);
+ perl_free(prober);
+ return status;
+}
+
+static gboolean
+load_perl_plugin(GaimPlugin *plugin)
+{
+ GaimPerlScript *gps = (GaimPerlScript *)plugin->info->extra_info;
+ char *atmp[3] = { plugin->path, NULL, NULL };
+
+ if (gps == NULL || gps->load_sub == NULL)
+ return FALSE;
+
+ gaim_debug(GAIM_DEBUG_INFO, "perl", "Loading perl script\n");
+
+ if (my_perl == NULL)
+ perl_init();
+
+ plugin->handle = gps;
+
+ atmp[1] = gps->package;
+
+ PERL_SET_CONTEXT(my_perl);
+ execute_perl("Gaim::PerlLoader::load_n_eval", 2, atmp);
+
+ {
+ dSP;
+ PERL_SET_CONTEXT(my_perl);
+ SPAGAIN;
+ ENTER;
+ SAVETMPS;
+ PUSHMARK(sp);
+ XPUSHs(sv_2mortal(gaim_perl_bless_object(plugin,
+ "Gaim::Plugin")));
+ PUTBACK;
+
+ perl_call_pv(gps->load_sub, G_EVAL | G_SCALAR);
+ SPAGAIN;
+
+ if (SvTRUE(ERRSV)) {
+ STRLEN len;
+
+ gaim_debug(GAIM_DEBUG_ERROR, "perl",
+ "Perl function %s exited abnormally: %s\n",
+ gps->load_sub, SvPV(ERRSV, len));
+ }
+
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+ }
+
+ return TRUE;
+}
+
+static void
+destroy_package(const char *package)
+{
+ dSP;
+ PERL_SET_CONTEXT(my_perl);
+ SPAGAIN;
+
+ ENTER;
+ SAVETMPS;
+
+ PUSHMARK(SP);
+ XPUSHs(sv_2mortal(newSVpv(package, strlen(package))));
+ PUTBACK;
+
+ perl_call_pv("Gaim::PerlLoader::destroy_package",
+ G_VOID | G_EVAL | G_DISCARD);
+
+ SPAGAIN;
+
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+}
+
+static gboolean
+unload_perl_plugin(GaimPlugin *plugin)
+{
+ GaimPerlScript *gps = (GaimPerlScript *)plugin->info->extra_info;
+
+ if (gps == NULL)
+ return FALSE;
+
+ gaim_debug(GAIM_DEBUG_INFO, "perl", "Unloading perl script\n");
+
+ if (gps->unload_sub != NULL) {
+ dSP;
+ PERL_SET_CONTEXT(my_perl);
+ SPAGAIN;
+ ENTER;
+ SAVETMPS;
+ PUSHMARK(sp);
+ XPUSHs(sv_2mortal(gaim_perl_bless_object(plugin,
+ "Gaim::Plugin")));
+ PUTBACK;
+
+ perl_call_pv(gps->unload_sub, G_EVAL | G_SCALAR);
+ SPAGAIN;
+
+ if (SvTRUE(ERRSV)) {
+ STRLEN len;
+
+ gaim_debug(GAIM_DEBUG_ERROR, "perl",
+ "Perl function %s exited abnormally: %s\n",
+ gps->load_sub, SvPV(ERRSV, len));
+ }
+
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+ }
+
+ gaim_perl_cmd_clear_for_plugin(plugin);
+ gaim_perl_signal_clear_for_plugin(plugin);
+ gaim_perl_timeout_clear_for_plugin(plugin);
+
+ destroy_package(gps->package);
+
+ return TRUE;
+}
+
+static void
+destroy_perl_plugin(GaimPlugin *plugin)
+{
+ if (plugin->info != NULL) {
+ GaimPerlScript *gps;
+
+ g_free(plugin->info->name);
+ g_free(plugin->info->version);
+ g_free(plugin->info->summary);
+ g_free(plugin->info->description);
+ g_free(plugin->info->author);
+ g_free(plugin->info->homepage);
+
+ gps = (GaimPerlScript *)plugin->info->extra_info;
+ if (gps != NULL) {
+ g_free(gps->load_sub);
+ g_free(gps->unload_sub);
+ g_free(gps->package);
+ g_free(gps->prefs_sub);
+#ifdef GAIM_GTKPERL
+ g_free(gps->gtk_prefs_sub);
+#endif
+ g_free(gps);
+ plugin->info->extra_info = NULL;
+ }
+ }
+}
+
+static gboolean
+plugin_load(GaimPlugin *plugin)
+{
+ return TRUE;
+}
+
+static gboolean
+plugin_unload(GaimPlugin *plugin)
+{
+ perl_end();
+
+ return TRUE;
+}
+
+static GaimPluginLoaderInfo loader_info =
+{
+ NULL, /**< exts */
+ probe_perl_plugin, /**< probe */
+ load_perl_plugin, /**< load */
+ unload_perl_plugin, /**< unload */
+ destroy_perl_plugin /**< destroy */
+};
+
+static GaimPluginInfo info =
+{
+ GAIM_PLUGIN_MAGIC,
+ GAIM_MAJOR_VERSION,
+ GAIM_MINOR_VERSION,
+ GAIM_PLUGIN_LOADER, /**< type */
+ NULL, /**< ui_requirement */
+ 0, /**< flags */
+ NULL, /**< dependencies */
+ GAIM_PRIORITY_DEFAULT, /**< priority */
+
+ PERL_PLUGIN_ID, /**< id */
+ N_("Perl Plugin Loader"), /**< name */
+ VERSION, /**< version */
+ N_("Provides support for loading perl plugins."), /**< summary */
+ N_("Provides support for loading perl plugins."), /**< description */
+ "Christian Hammond <chipx86@gnupdate.org>", /**< author */
+ GAIM_WEBSITE, /**< homepage */
+
+ plugin_load, /**< load */
+ plugin_unload, /**< unload */
+ NULL, /**< destroy */
+
+ NULL, /**< ui_info */
+ &loader_info, /**< extra_info */
+ NULL,
+ NULL
+};
+
+static void
+init_plugin(GaimPlugin *plugin)
+{
+ loader_info.exts = g_list_append(loader_info.exts, "pl");
+}
+
+GAIM_INIT_PLUGIN(perl, init_plugin, info)
diff --git a/libpurple/plugins/perl/scripts/account.pl b/libpurple/plugins/perl/scripts/account.pl
new file mode 100644
index 0000000000..e4bc2ca282
--- /dev/null
+++ b/libpurple/plugins/perl/scripts/account.pl
@@ -0,0 +1,122 @@
+$MODULE_NAME = "Account Functions Test";
+
+use Gaim;
+
+# All the information Gaim gets about our nifty plugin
+%PLUGIN_INFO = (
+ perl_api_version => 2,
+ name => "Perl: $MODULE_NAME",
+ version => "0.1",
+ summary => "Test plugin for the Perl interpreter.",
+ description => "Implements a set of test proccedures to ensure all " .
+ "functions that work in the C API still work in the " .
+ "Perl plugin interface. As XSUBs are added, this " .
+ "*should* be updated to test the changes. " .
+ "Furthermore, this will function as the tutorial perl " .
+ "plugin.",
+ author => "John H. Kelm <johnhkelm\@gmail.com>",
+ url => "http://sourceforge.net/users/johnhkelm/",
+
+ load => "plugin_load",
+ unload => "plugin_unload"
+);
+
+
+ # These names must already exist
+ my $USERNAME = "johnhkelm2";
+
+ # We will create these on load then destroy them on unload
+ my $TEST_NAME = "perlTestName";
+ my $PROTOCOL_ID = "prpl-oscar";
+
+
+sub plugin_init {
+ return %PLUGIN_INFO;
+}
+
+
+# This is the sub defined in %PLUGIN_INFO to be called when the plugin is loaded
+# Note: The plugin has a reference to itself on top of the argument stack.
+sub plugin_load {
+ my $plugin = shift;
+ print "#" x 80 . "\n\n";
+ Gaim::Debug::info($MODULE_NAME, "plugin_load() - Testing $MODULE_NAME Started.");
+ print "\n\n";
+
+
+ #################################
+ # #
+ # Gaim::Account::Option #
+ # #
+ #################################
+
+ print "Testing: Gaim::Account::Option::new()...\n";
+ $acc_opt = Gaim::Account::Option->new(1, "TEXT", "pref_name");
+ $acc_opt2 = Gaim::Account::Option->bool_new("TeXt", "MYprefName", 1);
+
+ #################################
+ # #
+ # Gaim::Account #
+ # #
+ #################################
+
+
+ print "Testing: Gaim::Account::new()... ";
+ $account = Gaim::Account->new($TEST_NAME, $PROTOCOL_ID);
+ if ($account) { print "ok.\n"; } else { print "fail.\n"; }
+
+ print "Testing: Gaim::Accounts::add()...";
+ Gaim::Accounts::add($account);
+ print "pending find...\n";
+
+ print "Testing: Gaim::Accounts::find()...";
+ $account = Gaim::Accounts::find($TEST_NAME, $PROTOCOL_ID);
+ if ($account) { print "ok.\n"; } else { print "fail.\n"; }
+
+ print "Testing: Gaim::Account::get_username()... ";
+ $user_name = $account->get_username();
+ if ($user_name) {
+ print "Success: $user_name.\n";
+ } else {
+ print "Failed!\n";
+ }
+
+ print "Testing: Gaim::Account::is_connected()... ";
+ if ($account->is_connected()) {
+ print " Connected.\n";
+ } else {
+ print " Disconnected.\n";
+ }
+
+ print "Testing: Gaim::Accounts::get_active_status()... ";
+ if ($account->get_active_status()) {
+ print "Okay.\n";
+ } else {
+ print "Failed!\n";
+ }
+
+ $account = Gaim::Accounts::find($USERNAME, $PROTOCOL_ID);
+ print "Testing: Gaim::Accounts::connect()...pending...\n";
+
+ $account->set_status("available", TRUE);
+ $account->connect();
+
+ print "\n\n";
+ Gaim::Debug::info($MODULE_NAME, "plugin_load() - Testing $MODULE_NAME Completed.\n");
+ print "\n\n" . "#" x 80 . "\n\n";
+}
+
+sub plugin_unload {
+ my $plugin = shift;
+
+ print "#" x 80 . "\n\n";
+ Gaim::Debug::info($MODULE_NAME, "plugin_unload() - Testing $MODULE_NAME Started.\n");
+ print "\n\n";
+
+ ######### TEST CODE HERE ##########
+
+ print "\n\n";
+ Gaim::Debug::info($MODULE_NAME, "plugin_unload() - Testing $MODULE_NAME Completed.\n");
+ print "\n\n" . "#" x 80 . "\n\n";
+}
+
diff --git a/libpurple/plugins/perl/scripts/buddy_list.pl b/libpurple/plugins/perl/scripts/buddy_list.pl
new file mode 100644
index 0000000000..3aef6487ae
--- /dev/null
+++ b/libpurple/plugins/perl/scripts/buddy_list.pl
@@ -0,0 +1,107 @@
+$MODULE_NAME = "Buddy List Test";
+
+use Gaim;
+
+# All the information Gaim gets about our nifty plugin
+%PLUGIN_INFO = (
+ perl_api_version => 2,
+ name => "Perl: $MODULE_NAME",
+ version => "0.1",
+ summary => "Test plugin for the Perl interpreter.",
+ description => "Implements a set of test proccedures to ensure all functions that work in the C API still work in the Perl plugin interface. As XSUBs are added, this *should* be updated to test the changes. Furthermore, this will function as the tutorial perl plugin.",
+ author => "John H. Kelm <johnhkelm\@gmail.com>",
+ url => "http://sourceforge.net/users/johnhkelm/",
+
+ load => "plugin_load",
+ unload => "plugin_unload"
+);
+
+
+ # These names must already exist
+ my $USERNAME = "johnhkelm2";
+
+ # We will create these on load then destroy them on unload
+ my $TEST_GROUP = "UConn Buddies";
+ my $TEST_NAME = "johnhkelm";
+ my $TEST_ALIAS = "John Kelm";
+ my $PROTOCOL_ID = "prpl-oscar";
+
+
+sub plugin_init {
+ return %PLUGIN_INFO;
+}
+
+
+# This is the sub defined in %PLUGIN_INFO to be called when the plugin is loaded
+# Note: The plugin has a reference to itself on top of the argument stack.
+sub plugin_load {
+ my $plugin = shift;
+
+ # This is how we get an account to use in the following tests. You should replace the username
+ # with an existing user
+ $account = Gaim::Accounts::find($USERNAME, $PROTOCOL_ID);
+
+ # Testing a find function: Note Gaim::Find not Gaim::Buddy:find!
+ # Furthermore, this should work the same for chats and groups
+ Gaim::Debug::info($MODULE_NAME, "Testing: Gaim::Find::buddy()...");
+ $buddy = Gaim::Find::buddy($account, $TEST_NAME);
+ Gaim::Debug::info("", ($buddy ? "ok." : "fail.") . "\n");
+
+ # If you should need the handle for some reason, here is how you do it
+ Gaim::Debug::info($MODULE_NAME, "Testing: Gaim::BuddyList::get_handle()...");
+ $handle = Gaim::BuddyList::get_handle();
+ Gaim::Debug::info("", ($handle ? "ok." : "fail.") . "\n");
+
+ # This gets the Gaim::BuddyList and references it by $blist
+ Gaim::Debug::info($MODULE_NAME, "Testing: Gaim::get_blist()...");
+ $blist = Gaim::get_blist();
+ Gaim::Debug::info("", ($blist ? "ok." : "fail.") . "\n");
+
+ # This is how you would add a buddy named $TEST_NAME" with the alias $TEST_ALIAS
+ Gaim::Debug::info($MODULE_NAME, "Testing: Gaim::BuddyList::Buddy::new...");
+ $buddy = Gaim::BuddyList::Buddy::new($account, $TEST_NAME, $TEST_ALIAS);
+ Gaim::Debug::info("", ($buddy ? "ok." : "fail.") . "\n");
+
+ # Here we add the new buddy '$buddy' to the group $TEST_GROUP
+ # so first we must find the group
+ Gaim::Debug::info($MODULE_NAME, "Testing: Gaim::Find::group...");
+ $group = Gaim::Find::group($TEST_GROUP);
+ Gaim::Debug::info("", ($group ? "ok." : "fail.") . "\n");
+
+ # To add the buddy we need to have the buddy, contact, group and node for insertion.
+ # For this example we can let contact be undef and set the insertion node as the group
+ Gaim::Debug::info($MODULE_NAME, "Testing: Gaim::BuddyList::add_buddy...\n");
+ Gaim::BuddyList::add_buddy($buddy, undef, $group, $group);
+
+ # The example that follows gives an indication of how an API call that returns a list is handled.
+ # In this case the buddies of the account found earlier are retrieved and put in an array '@buddy_array'
+ # Further down an accessor method is used, 'get_name()' -- see source for details on the full set of methods
+ Gaim::Debug::info($MODULE_NAME, "Testing: Gaim::Find::buddies...\n");
+ @buddy_array = Gaim::Find::buddies($account, undef);
+ if (@buddy_array) {
+ Gaim::Debug::info($MODULE_NAME, "Buddies in list (" . @buddy_array . "): \n");
+ foreach $bud (@buddy_array) {
+ Gaim::Debug::info($MODULE_NAME, Gaim::BuddyList::Buddy::get_name($bud) . "\n");
+ }
+ }
+}
+
+sub plugin_unload {
+ my $plugin = shift;
+
+ print "#" x 80 . "\n\n";
+ ######### TEST CODE HERE ##########
+
+ print "Testing: Gaim::Find::buddy()...";
+ $buddy = Gaim::Find::buddy($account, $TEST_NAME . TEST);
+ if ($buddy) {
+ print "ok.\n";
+ print "Testing: Gaim::BuddyList::remove_buddy()...";
+ Gaim::BuddyList::remove_buddy($buddy);
+ if (Gaim::Find::buddy($account, $TEST_NAME . TEST)) { print "fail.\n"; } else { print "ok.\n"; }
+ } else { print "fail.\n"; }
+
+
+ print "\n\n" . "#" x 80 . "\n\n";
+}
+
diff --git a/libpurple/plugins/perl/scripts/conversation.pl b/libpurple/plugins/perl/scripts/conversation.pl
new file mode 100644
index 0000000000..a04273f2be
--- /dev/null
+++ b/libpurple/plugins/perl/scripts/conversation.pl
@@ -0,0 +1,119 @@
+$MODULE_NAME = "Conversation Test";
+
+use Gaim;
+
+# All the information Gaim gets about our nifty plugin
+%PLUGIN_INFO = (
+ perl_api_version => 2,
+ name => "Perl: $MODULE_NAME",
+ version => "0.1",
+ summary => "Test plugin for the Perl interpreter.",
+ description => "Implements a set of test proccedures to ensure all " .
+ "functions that work in the C API still work in the " .
+ "Perl plugin interface. As XSUBs are added, this " .
+ "*should* be updated to test the changes. " .
+ "Furthermore, this will function as the tutorial perl " .
+ "plugin.",
+ author => "John H. Kelm <johnhkelm\@gmail.com>",
+ url => "http://sourceforge.net/users/johnhkelm/",
+
+ load => "plugin_load",
+ unload => "plugin_unload"
+);
+
+
+ # These names must already exist
+ my $GROUP = "UIUC Buddies";
+ my $USERNAME = "johnhkelm2";
+
+ # We will create these on load then destroy them on unload
+ my $TEST_GROUP = "UConn Buddies";
+ my $TEST_NAME = "johnhkelm";
+ my $TEST_ALIAS = "John Kelm";
+ my $PROTOCOL_ID = "prpl-oscar";
+
+
+sub plugin_init {
+ return %PLUGIN_INFO;
+}
+
+
+# This is the sub defined in %PLUGIN_INFO to be called when the plugin is loaded
+# Note: The plugin has a reference to itself on top of the argument stack.
+sub plugin_load {
+ my $plugin = shift;
+ print "#" x 80 . "\n\n";
+
+ print "PERL: Finding account.\n";
+ $account = Gaim::Accounts::find($USERNAME, $PROTOCOL_ID);
+
+ ######### TEST CODE HERE ##########
+ # First we create two new conversations.
+ print "Testing Gaim::Conversation::new()...";
+ $conv1 = Gaim::Conversation->new(1, $account, "Test Conversation 1");
+ if ($conv1) { print "ok.\n"; } else { print "fail.\n"; }
+
+ print "Testing Gaim::Conversation::new()...";
+ $conv2 = Gaim::Conversation->new(1, $account, "Test Conversation 2");
+ if ($conv2) { print "ok.\n"; } else { print "fail.\n"; }
+
+ # Second we create a window to display the conversations in.
+ # Note that the package here is Gaim::Conversation::Window
+ print "Testing Gaim::Conversation::Window::new()...\n";
+ $win = Gaim::Conversation::Window::new();
+
+ # The third thing to do is to add the two conversations to the windows.
+ # The subroutine add_conversation() returns the number of conversations
+ # present in the window.
+ print "Testing Gaim::Conversation::Window::add_conversation()...";
+ $conv_count = $conv1->add_conversation();
+ if ($conv_count) {
+ print "ok..." . $conv_count . " conversations...\n";
+ } else {
+ print "fail.\n";
+ }
+
+ print "Testing Gaim::Conversation::Window::add_conversation()...";
+ $conv_count = $win->add_conversation($conv2);
+ if ($conv_count) {
+ print "ok..." . $conv_count . " conversations...\n";
+ } else {
+ print "fail.\n";
+ }
+
+ # Now the window is displayed to the user.
+ print "Testing Gaim::Conversation::Window::show()...\n";
+ $win->show();
+
+ # Use get_im_data() to get a handle for the conversation
+ print "Testing Gaim::Conversation::get_im_data()...\n";
+ $im = $conv1->get_im_data();
+ if ($im) { print "ok.\n"; } else { print "fail.\n"; }
+
+ # Here we send messages to the conversation
+ print "Testing Gaim::Conversation::IM::send()...\n";
+ $im->send("Message Test.");
+
+ print "Testing Gaim::Conversation::IM::write()...\n";
+ $im->write("SENDER", "<b>Message</b> Test.", 0, 0);
+
+ print "#" x 80 . "\n\n";
+}
+
+sub plugin_unload {
+ my $plugin = shift;
+
+ print "#" x 80 . "\n\n";
+ ######### TEST CODE HERE ##########
+
+ print "Testing Gaim::Conversation::Window::get_conversation_count()...\n";
+ $conv_count = $win->get_conversation_count();
+ print "...and it returned $conv_count.\n";
+ if ($conv_count > 0) {
+ print "Testing Gaim::Conversation::Window::destroy()...\n";
+ $win->destroy();
+ }
+
+ print "\n\n" . "#" x 80 . "\n\n";
+}
+
diff --git a/libpurple/plugins/perl/scripts/count_down.pl b/libpurple/plugins/perl/scripts/count_down.pl
new file mode 100644
index 0000000000..d1a22fcded
--- /dev/null
+++ b/libpurple/plugins/perl/scripts/count_down.pl
@@ -0,0 +1,89 @@
+use Gaim;
+
+%PLUGIN_INFO = (
+ perl_api_version => 2,
+ name => "Countdown Info Timer",
+ version => "0.1",
+ summary => "Makes a countdown in days from today.",
+ description => "Long description coming....",
+ author => "John H. Kelm <johnhkelm\@gmail.com>",
+ url => "http://gaim.sourceforge.net/",
+
+ load => "plugin_load",
+ unload => "plugin_unload"
+);
+
+ $GLOBAL_TEST_VAR = "STUFF!";
+
+sub plugin_unload {
+ my $plugin = shift;
+}
+
+sub plugin_init {
+ return %PLUGIN_INFO;
+}
+
+
+sub plugin_load {
+ my $plugin = shift;
+
+ # Retrieve all the accounts
+ @accounts = Gaim::Accounts::get_all();
+
+ print "NUM OF ACCS: " . $accounts . "\n";
+ # Search each account's user info for our tag
+ foreach $acc (@accounts) {
+ print "IN ACCOUNTS\n";
+ $user_info = $acc->get_user_info();
+ print "USER INFO 1: " . $user_info . "\n";
+ # Find <countdown> and replace
+ $user_info =~ /countdown([0-9]+).([0-9]+).([0-9]+)/;
+ print "Found: " .$1 . " " . $2 . " " . $3 . "\n";
+ $days = count_days($1, $2, $3);
+ $user_info =~ s/countdown(\d\d\d\d).(\d\d).(\d\d)/$days/;
+ print "USER INFO 2: " . $user_info . "\n";
+ # $acc->set_user_info($user_info);
+
+ }
+
+ eval '
+ use Gtk2 \'-init\';
+ use Glib;
+ $window = Gtk2::Window->new(\'toplevel\');
+ $window->set_border_width(10);
+ $button = Gtk2::Button->new("Hello World");
+ $button->signal_connect(clicked => \&hello, $window);
+
+ $window->add($button);
+ $button->show;
+ $window->show;
+ # Gtk2->main;
+
+ 0;
+
+ '; warn $@ if $@;
+}
+
+sub hello {
+ my ($widget, $window) = @_;
+ print "Called from sub hello!\n ";
+ print "Test var: " . $GLOBAL_TEST_VAR . " \n";
+ @accounts = Gaim::Accounts::get_all();
+ $acc = $accounts[0];
+ $user_info = $acc->get_user_info();
+ print "USER INFO from sub hello: " . $user_info . "\n";
+ $window->destroy;
+}
+
+sub count_days {
+ ($year, $month, $day) = @_;
+
+
+ eval '
+ use Time::Local;
+ $future = timelocal(0,0,0,$day,$month-1,$year);
+ '; warn $@ if $@;
+ $today = time();
+ $days = int(($future - $today)/(60*60*24));
+ return $days;
+}
diff --git a/libpurple/plugins/perl/scripts/gtk_frame_test.pl b/libpurple/plugins/perl/scripts/gtk_frame_test.pl
new file mode 100644
index 0000000000..5569b39368
--- /dev/null
+++ b/libpurple/plugins/perl/scripts/gtk_frame_test.pl
@@ -0,0 +1,66 @@
+$MODULE_NAME = "GTK Frame Test";
+
+use Gaim;
+
+%PLUGIN_INFO = (
+ perl_api_version => 2,
+ name => " Perl: $MODULE_NAME",
+ version => "0.1",
+ summary => "Test plugin for the Perl interpreter.",
+ description => "Implements a set of test proccedures to ensure all functions that work in the C API still work in the Perl plugin interface. As XSUBs are added, this *should* be updated to test the changes. Furthermore, this will function as the tutorial perl plugin.",
+ author => "John H. Kelm <johnhkelm\@gmail.com>",
+ url => "http://gaim.sourceforge.net/",
+
+ GTK_UI => TRUE,
+ gtk_prefs_info => "foo",
+ load => "plugin_load",
+ unload => "plugin_unload",
+);
+
+
+sub plugin_init {
+ return %PLUGIN_INFO;
+}
+
+sub button_cb {
+ my $widget = shift;
+ my $data = shift;
+ print "Clicked button with message: " . $data . "\n";
+}
+
+sub foo {
+ eval '
+ use Glib;
+ use Gtk2 \'-init\';
+
+ $frame = Gtk2::Frame->new(\'Gtk Test Frame\');
+ $button = Gtk2::Button->new(\'Print Message\');
+
+ $frame->set_border_width(10);
+ $button->set_border_width(150);
+ $button->signal_connect("clicked" => \&button_cb, "Message Text");
+ $frame->add($button);
+
+ $button->show();
+ $frame->show();
+ ';
+ return $frame;
+}
+
+sub plugin_load {
+ my $plugin = shift;
+ print "#" x 80 . "\n";
+
+
+ ######### TEST CODE HERE ##########
+
+ print "$MODULE_NAME: Loading...\n";
+
+
+ Gaim::debug_info("plugin_load()", "Testing $MODULE_NAME Completed.");
+ print "#" x 80 . "\n\n";
+}
+
+sub plugin_unload {
+
+}
diff --git a/libpurple/plugins/perl/scripts/plugin_action.pl b/libpurple/plugins/perl/scripts/plugin_action.pl
new file mode 100644
index 0000000000..04f28edb08
--- /dev/null
+++ b/libpurple/plugins/perl/scripts/plugin_action.pl
@@ -0,0 +1,58 @@
+$MODULE_NAME = "Plugin Action Test Plugin";
+use Gaim;
+
+sub plugin_init {
+ return %PLUGIN_INFO;
+}
+
+sub plugin_load {
+ my $plugin = shift;
+}
+
+sub plugin_unload {
+ my $plugin = shift;
+}
+
+sub fun1 {
+ print "1\n";
+}
+
+sub fun2 {
+ print "2\n";
+}
+
+sub fun3 {
+ print "3\n";
+}
+
+%plugin_actions = (
+ "Action 1" => \&fun1,
+ "Action 2" => \&fun2,
+ "Action 3" => \&fun3
+# "Action 1" => sub { print "1\n"; },
+# "Action 2" => sub { print "2\n"; },
+# "Action 3" => sub { print "3\n"; }
+);
+
+sub plugin_action_names {
+ foreach $key (keys %plugin_actions) {
+ push @array, $key;
+ }
+
+ return @array;
+}
+
+# All the information Gaim gets about our nifty plugin
+%PLUGIN_INFO = (
+ perl_api_version => 2,
+ name => "Perl: $MODULE_NAME",
+ version => "0.1",
+ summary => "Test plugin for the Perl interpreter.",
+ description => "Just a basic test plugin template.",
+ author => "Etan Reisner <deryni\@gmail.com>",
+ url => "http://sourceforge.net/users/deryni9/",
+
+ load => "plugin_load",
+ unload => "plugin_unload",
+ plugin_action_sub => "plugin_action_names"
+);
diff --git a/libpurple/plugins/perl/scripts/plugin_pref.pl b/libpurple/plugins/perl/scripts/plugin_pref.pl
new file mode 100644
index 0000000000..4a763356b4
--- /dev/null
+++ b/libpurple/plugins/perl/scripts/plugin_pref.pl
@@ -0,0 +1,94 @@
+$MODULE_NAME = "Prefs Functions Test";
+use Gaim;
+# All the information Gaim gets about our nifty plugin
+%PLUGIN_INFO = (
+ perl_api_version => 2,
+ name => "Perl: $MODULE_NAME",
+ version => "0.1",
+ summary => "Test plugin for the Perl interpreter.",
+ description => "Implements a set of test proccedures to ensure all " .
+ "functions that work in the C API still work in the " .
+ "Perl plugin interface. As XSUBs are added, this " .
+ "*should* be updated to test the changes. " .
+ "Furthermore, this will function as the tutorial perl " .
+ "plugin.",
+ author => "John H. Kelm <johnhkelm\@gmail.com>",
+ url => "http://sourceforge.net/users/johnhkelm/",
+
+ load => "plugin_load",
+ unload => "plugin_unload",
+ prefs_info => "foo"
+);
+
+ # These names must already exist
+ my $GROUP = "UIUC Buddies";
+ my $USERNAME = "johnhkelm2";
+
+ # We will create these on load then destroy them on unload
+ my $TEST_GROUP = "perlTestGroup";
+ my $TEST_NAME = "perlTestName";
+ my $TEST_ALIAS = "perlTestAlias";
+ my $PROTOCOL_ID = "prpl-oscar";
+
+sub foo {
+ $frame = Gaim::PluginPref::Frame->new();
+
+ $ppref = Gaim::PluginPref->new_with_label("boolean");
+ $frame->add($ppref);
+
+ $ppref = Gaim::PluginPref->new_with_name_and_label(
+ "/plugins/core/perl_test/bool", "Boolean Preference");
+ $frame->add($ppref);
+
+
+ $ppref = Gaim::PluginPref->new_with_name_and_label(
+ "/plugins/core/perl_test/choice", "Choice Preference");
+ $ppref->set_type(1);
+ $ppref->add_choice("ch0", $frame);
+ $ppref->add_choice("ch1", $frame);
+ $frame->add($ppref);
+
+ $ppref = Gaim::PluginPref->new_with_name_and_label(
+ "/plugins/core/perl_test/text", "Text Box Preference");
+ $ppref->set_max_length(16);
+ $frame->add($ppref);
+
+ return $frame;
+}
+
+sub plugin_init {
+
+ return %PLUGIN_INFO;
+}
+
+
+# This is the sub defined in %PLUGIN_INFO to be called when the plugin is loaded
+# Note: The plugin has a reference to itself on top of the argument stack.
+sub plugin_load {
+ my $plugin = shift;
+ print "#" x 80 . "\n\n";
+
+
+ ######### TEST CODE HERE ##########
+
+ Gaim::Prefs::add_none("/plugins/core/perl_test");
+ Gaim::Prefs::add_bool("/plugins/core/perl_test/bool", 1);
+ Gaim::Prefs::add_string("/plugins/core/perl_test/choice", "ch1");
+ Gaim::Prefs::add_string("/plugins/core/perl_test/text", "Foobar");
+
+
+ print "\n\n" . "#" x 80 . "\n\n";
+}
+
+sub plugin_unload {
+ my $plugin = shift;
+
+ print "#" x 80 . "\n\n";
+
+
+ ######### TEST CODE HERE ##########
+
+
+ print "\n\n" . "#" x 80 . "\n\n";
+}
+
diff --git a/libpurple/plugins/perl/scripts/request.pl b/libpurple/plugins/perl/scripts/request.pl
new file mode 100644
index 0000000000..1bed69f28f
--- /dev/null
+++ b/libpurple/plugins/perl/scripts/request.pl
@@ -0,0 +1,109 @@
+$MODULE_NAME = "Request Functions Test";
+
+use Gaim;
+
+# All the information Gaim gets about our nifty plugin
+%PLUGIN_INFO = (
+ perl_api_version => 2,
+ name => "Perl: $MODULE_NAME",
+ version => "0.1",
+ summary => "Test plugin for the Perl interpreter.",
+ description => "Implements a set of test proccedures to ensure all functions that work in the C API still work in the Perl plugin interface. As XSUBs are added, this *should* be updated to test the changes. Furthermore, this will function as the tutorial perl plugin.",
+ author => "John H. Kelm <johnhkelm\@gmail.com>",
+ url => "http://sourceforge.net/users/johnhkelm/",
+
+ load => "plugin_load",
+ unload => "plugin_unload",
+ plugin_action_sub => "plugin_action_names"
+);
+
+%plugin_actions = (
+ "Plugin Action Test Label" => \&plugin_action_test,
+);
+
+sub plugin_action_names {
+ foreach $key (keys %plugin_actions) {
+ push @array, $key;
+ }
+
+ return @array;
+}
+
+sub plugin_init {
+ return %PLUGIN_INFO;
+}
+
+sub ok_cb_test {
+ $fields = shift;
+
+ Gaim::Debug::info($MODULE_NAME, "plugin_action_cb_test: BEGIN\n");
+ Gaim::Debug::info($MODULE_NAME, "ok_cb_test: BEGIN\n");
+ Gaim::Debug::info($MODULE_NAME, "ok_cb_test: Button Click\n");
+ Gaim::Debug::info($MODULE_NAME, "ok_cb_test: Field Type: $fields \n");
+ $account = Gaim::Request::Fields::get_account($fields, "acct_test");
+ Gaim::Debug::info($MODULE_NAME, "ok_cb_test: Username of selected account: " . Gaim::Account::get_username($account) . "\n");
+ $int = Gaim::Request::Fields::get_integer($fields, "int_test");
+ Gaim::Debug::info($MODULE_NAME, "ok_cb_test: Integer Value: $int \n");
+ $choice = Gaim::Request::Fields::get_choice($fields, "ch_test");
+ Gaim::Debug::info($MODULE_NAME, "ok_cb_test: Choice Value: $choice \n");
+ Gaim::Debug::info($MODULE_NAME, "ok_cb_test: END\n");
+}
+
+sub cancel_cb_test {
+ Gaim::Debug::info($MODULE_NAME, "cancel_cb_test: Button Click\n");
+}
+
+sub plugin_action_test {
+ $plugin = shift;
+ Gaim::Debug::info($MODULE_NAME, "plugin_action_cb_test: BEGIN\n");
+ plugin_request($plugin);
+ Gaim::Debug::info($MODULE_NAME, "plugin_action_cb_test: END\n");
+}
+
+sub plugin_load {
+ my $plugin = shift;
+ ######### TEST CODE HERE ##########
+
+
+}
+
+sub plugin_request {
+ $group = Gaim::Request::Field::Group::new("Group Name");
+ $field = Gaim::Request::Field::account_new("acct_test", "Account Text", undef);
+ Gaim::Request::Field::account_set_show_all($field, 0);
+ Gaim::Request::Field::Group::add_field($group, $field);
+
+ $field = Gaim::Request::Field::int_new("int_test", "Integer Text", 33);
+ Gaim::Request::Field::Group::add_field($group, $field);
+
+ # Test field choice
+ $field = Gaim::Request::Field::choice_new("ch_test", "Choice Text", 1);
+ Gaim::Request::Field::choice_add($field, "Choice 0");
+ Gaim::Request::Field::choice_add($field, "Choice 1");
+ Gaim::Request::Field::choice_add($field, "Choice 2");
+
+ Gaim::Request::Field::Group::add_field($group, $field);
+
+
+ $request = Gaim::Request::Fields::new();
+ Gaim::Request::Fields::add_group($request, $group);
+
+ Gaim::Request::fields(
+ $plugin,
+ "Request Title!",
+ "Primary Title",
+ "Secondary Title",
+ $request,
+ "Ok Text", "ok_cb_test",
+ "Cancel Text", "cancel_cb_test");
+}
+
+sub plugin_unload {
+ my $plugin = shift;
+ Gaim::Debug::info($MODULE_NAME, "#" x 80 . "\n");
+ ######### TEST CODE HERE ##########
+
+
+ Gaim::Debug::info($MODULE_NAME, "\n" . "#" x 80 . "\n");
+}
+
diff --git a/libpurple/plugins/pluginpref_example.c b/libpurple/plugins/pluginpref_example.c
new file mode 100644
index 0000000000..961c4d08da
--- /dev/null
+++ b/libpurple/plugins/pluginpref_example.c
@@ -0,0 +1,162 @@
+/*
+ * PluginPref Example Plugin
+ *
+ * Copyright (C) 2004, Gary Kramlich <amc_grim@users.sf.net>
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#ifndef GAIM_PLUGINS
+# define GAIM_PLUGINS
+#endif
+
+#include "internal.h"
+
+#include "plugin.h"
+#include "pluginpref.h"
+#include "prefs.h"
+#include "version.h"
+
+static GaimPluginPrefFrame *
+get_plugin_pref_frame(GaimPlugin *plugin) {
+ GaimPluginPrefFrame *frame;
+ GaimPluginPref *ppref;
+
+ frame = gaim_plugin_pref_frame_new();
+
+ ppref = gaim_plugin_pref_new_with_label("boolean");
+ gaim_plugin_pref_frame_add(frame, ppref);
+
+ ppref = gaim_plugin_pref_new_with_name_and_label(
+ "/plugins/core/pluginpref_example/bool",
+ "boolean pref");
+ gaim_plugin_pref_frame_add(frame, ppref);
+
+ ppref = gaim_plugin_pref_new_with_label("integer");
+ gaim_plugin_pref_frame_add(frame, ppref);
+
+ ppref = gaim_plugin_pref_new_with_name_and_label(
+ "/plugins/core/pluginpref_example/int",
+ "integer pref");
+ gaim_plugin_pref_set_bounds(ppref, 0, 255);
+ gaim_plugin_pref_frame_add(frame, ppref);
+
+ ppref = gaim_plugin_pref_new_with_name_and_label(
+ "/plugins/core/pluginpref_example/int_choice",
+ "integer choice");
+ gaim_plugin_pref_set_type(ppref, GAIM_PLUGIN_PREF_CHOICE);
+ gaim_plugin_pref_add_choice(ppref, "One", GINT_TO_POINTER(1));
+ gaim_plugin_pref_add_choice(ppref, "Two", GINT_TO_POINTER(2));
+ gaim_plugin_pref_add_choice(ppref, "Four", GINT_TO_POINTER(4));
+ gaim_plugin_pref_add_choice(ppref, "Eight", GINT_TO_POINTER(8));
+ gaim_plugin_pref_add_choice(ppref, "Sixteen", GINT_TO_POINTER(16));
+ gaim_plugin_pref_add_choice(ppref, "Thirty Two", GINT_TO_POINTER(32));
+ gaim_plugin_pref_add_choice(ppref, "Sixty Four", GINT_TO_POINTER(64));
+ gaim_plugin_pref_add_choice(ppref, "One Hundred Twenty Eight", GINT_TO_POINTER(128));
+ gaim_plugin_pref_frame_add(frame, ppref);
+
+ ppref = gaim_plugin_pref_new_with_label("string");
+ gaim_plugin_pref_frame_add(frame, ppref);
+
+ ppref = gaim_plugin_pref_new_with_name_and_label(
+ "/plugins/core/pluginpref_example/string",
+ "string pref");
+ gaim_plugin_pref_frame_add(frame, ppref);
+
+ ppref = gaim_plugin_pref_new_with_name_and_label(
+ "/plugins/core/pluginpref_example/masked_string",
+ "masked string");
+ gaim_plugin_pref_set_masked(ppref, TRUE);
+ gaim_plugin_pref_frame_add(frame, ppref);
+
+ ppref = gaim_plugin_pref_new_with_name_and_label(
+ "/plugins/core/pluginpref_example/max_string",
+ "string pref\n(max length of 16)");
+ gaim_plugin_pref_set_max_length(ppref, 16);
+ gaim_plugin_pref_frame_add(frame, ppref);
+
+ ppref = gaim_plugin_pref_new_with_name_and_label(
+ "/plugins/core/pluginpref_example/string_choice",
+ "string choice");
+ gaim_plugin_pref_set_type(ppref, GAIM_PLUGIN_PREF_CHOICE);
+ gaim_plugin_pref_add_choice(ppref, "red", "red");
+ gaim_plugin_pref_add_choice(ppref, "orange", "orange");
+ gaim_plugin_pref_add_choice(ppref, "yellow", "yellow");
+ gaim_plugin_pref_add_choice(ppref, "green", "green");
+ gaim_plugin_pref_add_choice(ppref, "blue", "blue");
+ gaim_plugin_pref_add_choice(ppref, "purple", "purple");
+ gaim_plugin_pref_frame_add(frame, ppref);
+
+ return frame;
+}
+
+static GaimPluginUiInfo prefs_info = {
+ get_plugin_pref_frame,
+ 0, /* page_num (Reserved) */
+ NULL /* frame (Reserved) */
+};
+
+static GaimPluginInfo info =
+{
+ GAIM_PLUGIN_MAGIC,
+ GAIM_MAJOR_VERSION,
+ GAIM_MINOR_VERSION,
+ GAIM_PLUGIN_STANDARD, /**< type */
+ NULL, /**< ui_requirement */
+ 0, /**< flags */
+ NULL, /**< dependencies */
+ GAIM_PRIORITY_DEFAULT, /**< priority */
+
+ "core-pluginpref_example", /**< id */
+ "Pluginpref Example", /**< name */
+ VERSION, /**< version */
+ /** summary */
+ "An example of how to use pluginprefs",
+ /** description */
+ "An example of how to use pluginprefs",
+ "Gary Kramlich <amc_grim@users.sf.net>", /**< author */
+ GAIM_WEBSITE, /**< homepage */
+
+ NULL, /**< load */
+ NULL, /**< unload */
+ NULL, /**< destroy */
+
+ NULL, /**< ui_info */
+ NULL, /**< extra_info */
+ &prefs_info, /**< prefs_info */
+ NULL
+};
+
+static void
+init_plugin(GaimPlugin *plugin)
+{
+ gaim_prefs_add_none("/plugins/core/pluginpref_example");
+ gaim_prefs_add_bool("/plugins/core/pluginpref_example/bool", TRUE);
+ gaim_prefs_add_int("/plugins/core/pluginpref_example/int", 0);
+ gaim_prefs_add_int("/plugins/core/pluginpref_example/int_choice", 1);
+ gaim_prefs_add_string("/plugins/core/pluginpref_example/string",
+ "string");
+ gaim_prefs_add_string("/plugins/core/pluginpref_example/max_string",
+ "max length string");
+ gaim_prefs_add_string("/plugins/core/pluginpref_example/masked_string", "masked");
+ gaim_prefs_add_string("/plugins/core/pluginpref_example/string_choice", "red");
+}
+
+GAIM_INIT_PLUGIN(ppexample, init_plugin, info)
diff --git a/libpurple/plugins/psychic.c b/libpurple/plugins/psychic.c
new file mode 100644
index 0000000000..134f777400
--- /dev/null
+++ b/libpurple/plugins/psychic.c
@@ -0,0 +1,164 @@
+
+
+#include "internal.h"
+
+#include "account.h"
+#include "blist.h"
+#include "conversation.h"
+#include "debug.h"
+#include "signals.h"
+#include "status.h"
+#include "version.h"
+
+#include "plugin.h"
+#include "pluginpref.h"
+#include "prefs.h"
+
+
+#define PLUGIN_ID "core-psychic"
+#define PLUGIN_NAME N_("Psychic Mode")
+#define PLUGIN_SUMMARY N_("Psychic mode for incoming conversation")
+#define PLUGIN_DESC N_("Causes conversation windows to appear as other" \
+ " users begin to message you. This works for" \
+ " AIM, ICQ, Jabber, Sametime, and Yahoo!")
+#define PLUGIN_AUTHOR "Christopher O'Brien <siege@preoccupied.net>"
+
+
+#define PREFS_BASE "/plugins/core/psychic"
+#define PREF_BUDDIES PREFS_BASE "/buddies_only"
+#define PREF_NOTICE PREFS_BASE "/show_notice"
+#define PREF_STATUS PREFS_BASE "/activate_online"
+#define PREF_RAISE PREFS_BASE "/raise_conv"
+
+
+static void
+buddy_typing_cb(GaimAccount *acct, const char *name, void *data) {
+ GaimConversation *gconv;
+
+ if(gaim_prefs_get_bool(PREF_STATUS) &&
+ ! gaim_status_is_available(gaim_account_get_active_status(acct))) {
+ gaim_debug_info("psychic", "not available, doing nothing\n");
+ return;
+ }
+
+ if(gaim_prefs_get_bool(PREF_BUDDIES) &&
+ ! gaim_find_buddy(acct, name)) {
+ gaim_debug_info("psychic", "not in blist, doing nothing\n");
+ return;
+ }
+
+ gconv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM, name, acct);
+ if(! gconv) {
+ gaim_debug_info("psychic", "no previous conversation exists\n");
+ gconv = gaim_conversation_new(GAIM_CONV_TYPE_IM, acct, name);
+
+ if(gaim_prefs_get_bool(PREF_RAISE)) {
+ gaim_conversation_present(gconv);
+ }
+
+ if(gaim_prefs_get_bool(PREF_NOTICE)) {
+
+ /* This is a quote from Star Wars. You should probably not
+ translate it literally. If you can't find a fitting cultural
+ reference in your language, consider translating something
+ like this instead: "You feel a new message coming." */
+ gaim_conversation_write(gconv, NULL,
+ _("You feel a disturbance in the force..."),
+ GAIM_MESSAGE_SYSTEM | GAIM_MESSAGE_NO_LOG | GAIM_MESSAGE_ACTIVE_ONLY,
+ time(NULL));
+ }
+
+ gaim_conv_im_set_typing_state(GAIM_CONV_IM(gconv), GAIM_TYPING);
+ }
+}
+
+
+static GaimPluginPrefFrame *
+get_plugin_pref_frame(GaimPlugin *plugin) {
+
+ GaimPluginPrefFrame *frame;
+ GaimPluginPref *pref;
+
+ frame = gaim_plugin_pref_frame_new();
+
+ pref = gaim_plugin_pref_new_with_name(PREF_BUDDIES);
+ gaim_plugin_pref_set_label(pref, _("Only enable for users on"
+ " the buddy list"));
+ gaim_plugin_pref_frame_add(frame, pref);
+
+ pref = gaim_plugin_pref_new_with_name(PREF_STATUS);
+ gaim_plugin_pref_set_label(pref, _("Disable when away"));
+ gaim_plugin_pref_frame_add(frame, pref);
+
+ pref = gaim_plugin_pref_new_with_name(PREF_NOTICE);
+ gaim_plugin_pref_set_label(pref, _("Display notification message in"
+ " conversations"));
+ gaim_plugin_pref_frame_add(frame, pref);
+
+ pref = gaim_plugin_pref_new_with_name(PREF_RAISE);
+ gaim_plugin_pref_set_label(pref, _("Raise psychic conversations"));
+ gaim_plugin_pref_frame_add(frame, pref);
+
+ return frame;
+}
+
+
+static gboolean
+plugin_load(GaimPlugin *plugin) {
+
+ void *convs_handle;
+ convs_handle = gaim_conversations_get_handle();
+
+ gaim_signal_connect(convs_handle, "buddy-typing", plugin,
+ GAIM_CALLBACK(buddy_typing_cb), NULL);
+
+ return TRUE;
+}
+
+
+static GaimPluginUiInfo prefs_info = {
+ get_plugin_pref_frame,
+ 0, /* page_num (Reserved) */
+ NULL, /* frame (Reserved) */
+};
+
+
+static GaimPluginInfo info = {
+ GAIM_PLUGIN_MAGIC,
+ GAIM_MAJOR_VERSION,
+ GAIM_MINOR_VERSION,
+ GAIM_PLUGIN_STANDARD, /**< type */
+ NULL, /**< ui_requirement */
+ 0, /**< flags */
+ NULL, /**< dependencies */
+ GAIM_PRIORITY_DEFAULT, /**< priority */
+
+ PLUGIN_ID, /**< id */
+ PLUGIN_NAME, /**< name */
+ VERSION, /**< version */
+ PLUGIN_SUMMARY, /**< summary */
+ PLUGIN_DESC, /**< description */
+ PLUGIN_AUTHOR, /**< author */
+ GAIM_WEBSITE, /**< homepage */
+
+ plugin_load, /**< load */
+ NULL, /**< unload */
+ NULL, /**< destroy */
+
+ NULL, /**< ui_info */
+ NULL, /**< extra_info */
+ &prefs_info, /**< prefs_info */
+ NULL, /**< actions */
+};
+
+
+static void
+init_plugin(GaimPlugin *plugin) {
+ gaim_prefs_add_none(PREFS_BASE);
+ gaim_prefs_add_bool(PREF_BUDDIES, FALSE);
+ gaim_prefs_add_bool(PREF_NOTICE, TRUE);
+ gaim_prefs_add_bool(PREF_STATUS, TRUE);
+}
+
+
+GAIM_INIT_PLUGIN(psychic, init_plugin, info)
diff --git a/libpurple/plugins/signals-test.c b/libpurple/plugins/signals-test.c
new file mode 100644
index 0000000000..7bac24131d
--- /dev/null
+++ b/libpurple/plugins/signals-test.c
@@ -0,0 +1,708 @@
+/*
+ * Signals test plugin.
+ *
+ * Copyright (C) 2003 Christian Hammond.
+ *
+ * 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.
+ */
+#define SIGNAL_TEST_PLUGIN_ID "core-signals-test"
+
+#include <stdio.h>
+
+#include "internal.h"
+#include "cipher.h"
+#include "connection.h"
+#include "conversation.h"
+#include "core.h"
+#include "debug.h"
+#include "ft.h"
+#include "signals.h"
+#include "version.h"
+#include "status.h"
+#include "sound.h"
+
+/**************************************************************************
+ * Account subsystem signal callbacks
+ **************************************************************************/
+static void
+account_connecting_cb(GaimAccount *account, void *data)
+{
+ gaim_debug_misc("signals test", "account-connecting (%s)\n",
+ gaim_account_get_username(account));
+}
+
+static void
+account_setting_info_cb(GaimAccount *account, const char *info, void *data)
+{
+ gaim_debug_misc("signals test", "account-setting-info (%s, %s)\n",
+ gaim_account_get_username(account), info);
+}
+
+static void
+account_set_info_cb(GaimAccount *account, const char *info, void *data)
+{
+ gaim_debug_misc("signals test", "account-set-info (%s, %s)\n",
+ gaim_account_get_username(account), info);
+}
+
+static void
+account_status_changed(GaimAccount *account, GaimStatus *old, GaimStatus *new,
+ gpointer data)
+{
+ gaim_debug_misc("signals test", "account-status-changed (%s, %s, %s)\n",
+ gaim_account_get_username(account),
+ gaim_status_get_name(old),
+ gaim_status_get_name(new));
+}
+
+static void
+account_alias_changed(GaimAccount *account, const char *old, gpointer data)
+{
+ gaim_debug_misc("signals test", "account-alias-changed (%s, %s, %s)\n",
+ gaim_account_get_username(account),
+ old, gaim_account_get_alias(account));
+}
+
+/**************************************************************************
+ * Buddy Icons signal callbacks
+ **************************************************************************/
+static void
+buddy_icon_changed_cb(GaimBuddy *buddy)
+{
+ gaim_debug_misc("signals test", "buddy icon changed (%s)\n",
+ gaim_buddy_get_name(buddy));
+}
+
+/**************************************************************************
+ * Buddy List subsystem signal callbacks
+ **************************************************************************/
+static void
+buddy_status_changed_cb(GaimBuddy *buddy, GaimStatus *old_status,
+ GaimStatus *status, void *data)
+{
+ gaim_debug_misc("signals test", "buddy-status-changed (%s %s to %s)\n",
+ buddy->name, gaim_status_get_id(old_status),
+ gaim_status_get_id(status));
+}
+
+static void
+buddy_idle_changed_cb(GaimBuddy *buddy, gboolean old_idle, gboolean idle,
+ void *data)
+{
+ gaim_debug_misc("signals test", "buddy-idle-changed (%s %s)\n",
+ buddy->name, old_idle ? "unidled" : "idled");
+}
+
+static void
+buddy_signed_on_cb(GaimBuddy *buddy, void *data)
+{
+ gaim_debug_misc("signals test", "buddy-signed-on (%s)\n", buddy->name);
+}
+
+static void
+buddy_signed_off_cb(GaimBuddy *buddy, void *data)
+{
+ gaim_debug_misc("signals test", "buddy-signed-off (%s)\n", buddy->name);
+}
+
+static void
+buddy_added_cb(GaimBuddy *buddy, void *data)
+{
+ gaim_debug_misc("signals test", "buddy_added_cb (%s)\n", gaim_buddy_get_name(buddy));
+}
+
+static void
+buddy_removed_cb(GaimBuddy *buddy, void *data)
+{
+ gaim_debug_misc("signals test", "buddy_removed_cb (%s)\n", gaim_buddy_get_name(buddy));
+}
+
+static void
+blist_node_aliased(GaimBlistNode *node, const char *old_alias)
+{
+ GaimContact *p = (GaimContact *)node;
+ GaimBuddy *b = (GaimBuddy *)node;
+ GaimChat *c = (GaimChat *)node;
+ GaimGroup *g = (GaimGroup *)node;
+
+ if (GAIM_BLIST_NODE_IS_CONTACT(node))
+ gaim_debug_misc("signals test", "blist-node-aliased (Contact: %s, %s)\n", p->alias, old_alias);
+ else if (GAIM_BLIST_NODE_IS_BUDDY(node))
+ gaim_debug_misc("signals test", "blist-node-aliased (Buddy: %s, %s)\n", b->name, old_alias);
+ else if (GAIM_BLIST_NODE_IS_CHAT(node))
+ gaim_debug_misc("signals test", "blist-node-aliased (Chat: %s, %s)\n", c->alias, old_alias);
+ else if (GAIM_BLIST_NODE_IS_GROUP(node))
+ gaim_debug_misc("signals test", "blist-node-aliased (Group: %s, %s)\n", g->name, old_alias);
+ else
+ gaim_debug_misc("signals test", "blist-node-aliased (UNKNOWN: %d, %s)\n", node->type, old_alias);
+
+}
+
+static void
+blist_node_extended_menu_cb(GaimBlistNode *node, void *data)
+{
+ GaimContact *p = (GaimContact *)node;
+ GaimBuddy *b = (GaimBuddy *)node;
+ GaimChat *c = (GaimChat *)node;
+ GaimGroup *g = (GaimGroup *)node;
+
+ if (GAIM_BLIST_NODE_IS_CONTACT(node))
+ gaim_debug_misc("signals test", "blist-node-extended-menu (Contact: %s)\n", p->alias);
+ else if (GAIM_BLIST_NODE_IS_BUDDY(node))
+ gaim_debug_misc("signals test", "blist-node-extended-menu (Buddy: %s)\n", b->name);
+ else if (GAIM_BLIST_NODE_IS_CHAT(node))
+ gaim_debug_misc("signals test", "blist-node-extended-menu (Chat: %s)\n", c->alias);
+ else if (GAIM_BLIST_NODE_IS_GROUP(node))
+ gaim_debug_misc("signals test", "blist-node-extended-menu (Group: %s)\n", g->name);
+ else
+ gaim_debug_misc("signals test", "blist-node-extended-menu (UNKNOWN: %d)\n", node->type);
+
+}
+
+
+/**************************************************************************
+ * Connection subsystem signal callbacks
+ **************************************************************************/
+static void
+signing_on_cb(GaimConnection *gc, void *data)
+{
+ gaim_debug_misc("signals test", "signing-on (%s)\n",
+ gaim_account_get_username(gaim_connection_get_account(gc)));
+}
+
+static void
+signed_on_cb(GaimConnection *gc, void *data)
+{
+ gaim_debug_misc("signals test", "signed-on (%s)\n",
+ gaim_account_get_username(gaim_connection_get_account(gc)));
+}
+
+static void
+signing_off_cb(GaimConnection *gc, void *data)
+{
+ gaim_debug_misc("signals test", "signing-off (%s)\n",
+ gaim_account_get_username(gaim_connection_get_account(gc)));
+}
+
+static void
+signed_off_cb(GaimConnection *gc, void *data)
+{
+ gaim_debug_misc("signals test", "signed-off (%s)\n",
+ gaim_account_get_username(gaim_connection_get_account(gc)));
+}
+
+/**************************************************************************
+ * Conversation subsystem signal callbacks
+ **************************************************************************/
+static gboolean
+writing_im_msg_cb(GaimAccount *account, const char *who, char **buffer,
+ GaimConversation *conv, GaimMessageFlags flags, void *data)
+{
+ gaim_debug_misc("signals test", "writing-im-msg (%s, %s, %s)\n",
+ gaim_account_get_username(account), gaim_conversation_get_name(conv), *buffer);
+
+ return FALSE;
+
+}
+
+static void
+wrote_im_msg_cb(GaimAccount *account, const char *who, const char *buffer,
+ GaimConversation *conv, GaimMessageFlags flags, void *data)
+{
+ gaim_debug_misc("signals test", "wrote-im-msg (%s, %s, %s)\n",
+ gaim_account_get_username(account), gaim_conversation_get_name(conv), buffer);
+}
+
+static void
+sending_im_msg_cb(GaimAccount *account, char *recipient, char **buffer, void *data)
+{
+ gaim_debug_misc("signals test", "sending-im-msg (%s, %s, %s)\n",
+ gaim_account_get_username(account), recipient, *buffer);
+
+}
+
+static void
+sent_im_msg_cb(GaimAccount *account, const char *recipient, const char *buffer, void *data)
+{
+ gaim_debug_misc("signals test", "sent-im-msg (%s, %s, %s)\n",
+ gaim_account_get_username(account), recipient, buffer);
+}
+
+static gboolean
+receiving_im_msg_cb(GaimAccount *account, char **sender, char **buffer,
+ GaimConversation *conv, GaimMessageFlags *flags, void *data)
+{
+ gaim_debug_misc("signals test", "receiving-im-msg (%s, %s, %s, %s, %d)\n",
+ gaim_account_get_username(account), *sender, *buffer,
+ (conv != NULL) ? gaim_conversation_get_name(conv) : "(null)", *flags);
+
+ return FALSE;
+}
+
+static void
+received_im_msg_cb(GaimAccount *account, char *sender, char *buffer,
+ GaimConversation *conv, GaimMessageFlags flags, void *data)
+{
+ gaim_debug_misc("signals test", "received-im-msg (%s, %s, %s, %s, %d)\n",
+ gaim_account_get_username(account), sender, buffer,
+ (conv != NULL) ? gaim_conversation_get_name(conv) : "(null)", flags);
+}
+
+static gboolean
+writing_chat_msg_cb(GaimAccount *account, const char *who, char **buffer,
+ GaimConversation *conv, GaimMessageFlags flags, void *data)
+{
+ gaim_debug_misc("signals test", "writing-chat-msg (%s, %s)\n",
+ gaim_conversation_get_name(conv), *buffer);
+
+ return FALSE;
+}
+
+static void
+wrote_chat_msg_cb(GaimAccount *account, const char *who, const char *buffer,
+ GaimConversation *conv, GaimMessageFlags flags, void *data)
+{
+ gaim_debug_misc("signals test", "wrote-chat-msg (%s, %s)\n",
+ gaim_conversation_get_name(conv), buffer);
+}
+
+static gboolean
+sending_chat_msg_cb(GaimAccount *account, char **buffer, int id, void *data)
+{
+ gaim_debug_misc("signals test", "sending-chat-msg (%s, %s, %d)\n",
+ gaim_account_get_username(account), *buffer, id);
+
+ return FALSE;
+}
+
+static void
+sent_chat_msg_cb(GaimAccount *account, const char *buffer, int id, void *data)
+{
+ gaim_debug_misc("signals test", "sent-chat-msg (%s, %s, %d)\n",
+ gaim_account_get_username(account), buffer, id);
+}
+
+static gboolean
+receiving_chat_msg_cb(GaimAccount *account, char **sender, char **buffer,
+ GaimConversation *chat, GaimMessageFlags *flags, void *data)
+{
+ gaim_debug_misc("signals test",
+ "receiving-chat-msg (%s, %s, %s, %s, %d)\n",
+ gaim_account_get_username(account), *sender, *buffer,
+ gaim_conversation_get_name(chat), *flags);
+
+ return FALSE;
+}
+
+static void
+received_chat_msg_cb(GaimAccount *account, char *sender, char *buffer,
+ GaimConversation *chat, GaimMessageFlags flags, void *data)
+{
+ gaim_debug_misc("signals test",
+ "received-chat-msg (%s, %s, %s, %s, %d)\n",
+ gaim_account_get_username(account), sender, buffer,
+ gaim_conversation_get_name(chat), flags);
+}
+
+static void
+conversation_created_cb(GaimConversation *conv, void *data)
+{
+ gaim_debug_misc("signals test", "conversation-created (%s)\n",
+ gaim_conversation_get_name(conv));
+}
+
+static void
+deleting_conversation_cb(GaimConversation *conv, void *data)
+{
+ gaim_debug_misc("signals test", "deleting-conversation (%s)\n",
+ gaim_conversation_get_name(conv));
+}
+
+static void
+buddy_typing_cb(GaimAccount *account, const char *name, void *data)
+{
+ gaim_debug_misc("signals test", "buddy-typing (%s, %s)\n",
+ gaim_account_get_username(account), name);
+}
+
+static void
+buddy_typing_stopped_cb(GaimAccount *account, const char *name, void *data)
+{
+ gaim_debug_misc("signals test", "buddy-typing-stopped (%s, %s)\n",
+ gaim_account_get_username(account), name);
+}
+
+static gboolean
+chat_buddy_joining_cb(GaimConversation *conv, const char *user,
+ GaimConvChatBuddyFlags flags, void *data)
+{
+ gaim_debug_misc("signals test", "chat-buddy-joining (%s, %s, %d)\n",
+ gaim_conversation_get_name(conv), user, flags);
+
+ return FALSE;
+}
+
+static void
+chat_buddy_joined_cb(GaimConversation *conv, const char *user,
+ GaimConvChatBuddyFlags flags, gboolean new_arrival, void *data)
+{
+ gaim_debug_misc("signals test", "chat-buddy-joined (%s, %s, %d, %d)\n",
+ gaim_conversation_get_name(conv), user, flags, new_arrival);
+}
+
+static void
+chat_buddy_flags_cb(GaimConversation *conv, const char *user,
+ GaimConvChatBuddyFlags oldflags, GaimConvChatBuddyFlags newflags, void *data)
+{
+ gaim_debug_misc("signals test", "chat-buddy-flags (%s, %s, %d, %d)\n",
+ gaim_conversation_get_name(conv), user, oldflags, newflags);
+}
+
+static gboolean
+chat_buddy_leaving_cb(GaimConversation *conv, const char *user,
+ const char *reason, void *data)
+{
+ gaim_debug_misc("signals test", "chat-buddy-leaving (%s, %s, %s)\n",
+ gaim_conversation_get_name(conv), user, reason);
+
+ return FALSE;
+}
+
+static void
+chat_buddy_left_cb(GaimConversation *conv, const char *user,
+ const char *reason, void *data)
+{
+ gaim_debug_misc("signals test", "chat-buddy-left (%s, %s, %s)\n",
+ gaim_conversation_get_name(conv), user, reason);
+}
+
+static void
+chat_inviting_user_cb(GaimConversation *conv, const char *name,
+ char **reason, void *data)
+{
+ gaim_debug_misc("signals test", "chat-inviting-user (%s, %s, %s)\n",
+ gaim_conversation_get_name(conv), name, *reason);
+}
+
+static void
+chat_invited_user_cb(GaimConversation *conv, const char *name,
+ const char *reason, void *data)
+{
+ gaim_debug_misc("signals test", "chat-invited-user (%s, %s, %s)\n",
+ gaim_conversation_get_name(conv), name, reason);
+}
+
+static gint
+chat_invited_cb(GaimAccount *account, const char *inviter,
+ const char *room_name, const char *message,
+ const GHashTable *components, void *data)
+{
+ gaim_debug_misc("signals test", "chat-invited (%s, %s, %s, %s)\n",
+ gaim_account_get_username(account), inviter,
+ room_name, message);
+
+ return 0;
+}
+
+static void
+chat_joined_cb(GaimConversation *conv, void *data)
+{
+ gaim_debug_misc("signals test", "chat-joined (%s)\n",
+ gaim_conversation_get_name(conv));
+}
+
+static void
+chat_left_cb(GaimConversation *conv, void *data)
+{
+ gaim_debug_misc("signals test", "chat-left (%s)\n",
+ gaim_conversation_get_name(conv));
+}
+
+static void
+chat_topic_changed_cb(GaimConversation *conv, const char *who,
+ const char *topic, void *data)
+{
+ gaim_debug_misc("signals test",
+ "chat-topic-changed (%s topic changed to: \"%s\" by %s)\n",
+ gaim_conversation_get_name(conv), topic,
+ (who) ? who : "unknown");
+}
+/**************************************************************************
+ * Ciphers signal callbacks
+ **************************************************************************/
+static void
+cipher_added_cb(GaimCipher *cipher, void *data) {
+ gaim_debug_misc("signals test", "cipher %s added\n",
+ gaim_cipher_get_name(cipher));
+}
+
+static void
+cipher_removed_cb(GaimCipher *cipher, void *data) {
+ gaim_debug_misc("signals test", "cipher %s removed\n",
+ gaim_cipher_get_name(cipher));
+}
+
+/**************************************************************************
+ * Core signal callbacks
+ **************************************************************************/
+static void
+quitting_cb(void *data)
+{
+ gaim_debug_misc("signals test", "quitting ()\n");
+}
+
+/**************************************************************************
+ * File transfer signal callbacks
+ **************************************************************************/
+static void
+ft_recv_accept_cb(GaimXfer *xfer, gpointer data) {
+ gaim_debug_misc("signals test", "file receive accepted\n");
+}
+
+static void
+ft_send_accept_cb(GaimXfer *xfer, gpointer data) {
+ gaim_debug_misc("signals test", "file send accepted\n");
+}
+
+static void
+ft_recv_start_cb(GaimXfer *xfer, gpointer data) {
+ gaim_debug_misc("signals test", "file receive started\n");
+}
+
+static void
+ft_send_start_cb(GaimXfer *xfer, gpointer data) {
+ gaim_debug_misc("signals test", "file send started\n");
+}
+
+static void
+ft_recv_cancel_cb(GaimXfer *xfer, gpointer data) {
+ gaim_debug_misc("signals test", "file receive canceled\n");
+}
+
+static void
+ft_send_cancel_cb(GaimXfer *xfer, gpointer data) {
+ gaim_debug_misc("signals test", "file send canceled\n");
+}
+
+static void
+ft_recv_complete_cb(GaimXfer *xfer, gpointer data) {
+ gaim_debug_misc("signals test", "file receive completed\n");
+}
+
+static void
+ft_send_complete_cb(GaimXfer *xfer, gpointer data) {
+ gaim_debug_misc("signals test", "file send completed\n");
+}
+
+/**************************************************************************
+ * Sound signal callbacks
+ **************************************************************************/
+static int
+sound_playing_event_cb(GaimSoundEventID event, const GaimAccount *account) {
+ if (account != NULL)
+ gaim_debug_misc("signals test", "sound playing event: %d for account: %s\n",
+ event, gaim_account_get_username(account));
+ else
+ gaim_debug_misc("signals test", "sound playing event: %d\n", event);
+
+ return 0;
+}
+
+/**************************************************************************
+ * Plugin stuff
+ **************************************************************************/
+static gboolean
+plugin_load(GaimPlugin *plugin)
+{
+ void *core_handle = gaim_get_core();
+ void *blist_handle = gaim_blist_get_handle();
+ void *conn_handle = gaim_connections_get_handle();
+ void *conv_handle = gaim_conversations_get_handle();
+ void *accounts_handle = gaim_accounts_get_handle();
+ void *ciphers_handle = gaim_ciphers_get_handle();
+ void *ft_handle = gaim_xfers_get_handle();
+ void *sound_handle = gaim_sounds_get_handle();
+
+ /* Accounts subsystem signals */
+ gaim_signal_connect(accounts_handle, "account-connecting",
+ plugin, GAIM_CALLBACK(account_connecting_cb), NULL);
+ gaim_signal_connect(accounts_handle, "account-setting-info",
+ plugin, GAIM_CALLBACK(account_setting_info_cb), NULL);
+ gaim_signal_connect(accounts_handle, "account-set-info",
+ plugin, GAIM_CALLBACK(account_set_info_cb), NULL);
+ gaim_signal_connect(accounts_handle, "account-status-changed",
+ plugin, GAIM_CALLBACK(account_status_changed), NULL);
+ gaim_signal_connect(accounts_handle, "account-alias-changed",
+ plugin, GAIM_CALLBACK(account_alias_changed), NULL);
+
+ /* Buddy List subsystem signals */
+ gaim_signal_connect(blist_handle, "buddy-status-changed",
+ plugin, GAIM_CALLBACK(buddy_status_changed_cb), NULL);
+ gaim_signal_connect(blist_handle, "buddy-idle-changed",
+ plugin, GAIM_CALLBACK(buddy_idle_changed_cb), NULL);
+ gaim_signal_connect(blist_handle, "buddy-signed-on",
+ plugin, GAIM_CALLBACK(buddy_signed_on_cb), NULL);
+ gaim_signal_connect(blist_handle, "buddy-signed-off",
+ plugin, GAIM_CALLBACK(buddy_signed_off_cb), NULL);
+ gaim_signal_connect(blist_handle, "buddy-added",
+ plugin, GAIM_CALLBACK(buddy_added_cb), NULL);
+ gaim_signal_connect(blist_handle, "buddy-removed",
+ plugin, GAIM_CALLBACK(buddy_removed_cb), NULL);
+ gaim_signal_connect(blist_handle, "buddy-icon-changed",
+ plugin, GAIM_CALLBACK(buddy_icon_changed_cb), NULL);
+ gaim_signal_connect(blist_handle, "blist-node-aliased",
+ plugin, GAIM_CALLBACK(blist_node_aliased), NULL);
+ gaim_signal_connect(blist_handle, "blist-node-extended-menu",
+ plugin, GAIM_CALLBACK(blist_node_extended_menu_cb), NULL);
+
+ /* Connection subsystem signals */
+ gaim_signal_connect(conn_handle, "signing-on",
+ plugin, GAIM_CALLBACK(signing_on_cb), NULL);
+ gaim_signal_connect(conn_handle, "signed-on",
+ plugin, GAIM_CALLBACK(signed_on_cb), NULL);
+ gaim_signal_connect(conn_handle, "signing-off",
+ plugin, GAIM_CALLBACK(signing_off_cb), NULL);
+ gaim_signal_connect(conn_handle, "signed-off",
+ plugin, GAIM_CALLBACK(signed_off_cb), NULL);
+
+ /* Conversations subsystem signals */
+ gaim_signal_connect(conv_handle, "writing-im-msg",
+ plugin, GAIM_CALLBACK(writing_im_msg_cb), NULL);
+ gaim_signal_connect(conv_handle, "wrote-im-msg",
+ plugin, GAIM_CALLBACK(wrote_im_msg_cb), NULL);
+ gaim_signal_connect(conv_handle, "sending-im-msg",
+ plugin, GAIM_CALLBACK(sending_im_msg_cb), NULL);
+ gaim_signal_connect(conv_handle, "sent-im-msg",
+ plugin, GAIM_CALLBACK(sent_im_msg_cb), NULL);
+ gaim_signal_connect(conv_handle, "receiving-im-msg",
+ plugin, GAIM_CALLBACK(receiving_im_msg_cb), NULL);
+ gaim_signal_connect(conv_handle, "received-im-msg",
+ plugin, GAIM_CALLBACK(received_im_msg_cb), NULL);
+ gaim_signal_connect(conv_handle, "writing-chat-msg",
+ plugin, GAIM_CALLBACK(writing_chat_msg_cb), NULL);
+ gaim_signal_connect(conv_handle, "wrote-chat-msg",
+ plugin, GAIM_CALLBACK(wrote_chat_msg_cb), NULL);
+ gaim_signal_connect(conv_handle, "sending-chat-msg",
+ plugin, GAIM_CALLBACK(sending_chat_msg_cb), NULL);
+ gaim_signal_connect(conv_handle, "sent-chat-msg",
+ plugin, GAIM_CALLBACK(sent_chat_msg_cb), NULL);
+ gaim_signal_connect(conv_handle, "receiving-chat-msg",
+ plugin, GAIM_CALLBACK(receiving_chat_msg_cb), NULL);
+ gaim_signal_connect(conv_handle, "received-chat-msg",
+ plugin, GAIM_CALLBACK(received_chat_msg_cb), NULL);
+ gaim_signal_connect(conv_handle, "conversation-created",
+ plugin, GAIM_CALLBACK(conversation_created_cb), NULL);
+ gaim_signal_connect(conv_handle, "deleting-conversation",
+ plugin, GAIM_CALLBACK(deleting_conversation_cb), NULL);
+ gaim_signal_connect(conv_handle, "buddy-typing",
+ plugin, GAIM_CALLBACK(buddy_typing_cb), NULL);
+ gaim_signal_connect(conv_handle, "buddy-typing-stopped",
+ plugin, GAIM_CALLBACK(buddy_typing_stopped_cb), NULL);
+ gaim_signal_connect(conv_handle, "chat-buddy-joining",
+ plugin, GAIM_CALLBACK(chat_buddy_joining_cb), NULL);
+ gaim_signal_connect(conv_handle, "chat-buddy-joined",
+ plugin, GAIM_CALLBACK(chat_buddy_joined_cb), NULL);
+ gaim_signal_connect(conv_handle, "chat-buddy-flags",
+ plugin, GAIM_CALLBACK(chat_buddy_flags_cb), NULL);
+ gaim_signal_connect(conv_handle, "chat-buddy-leaving",
+ plugin, GAIM_CALLBACK(chat_buddy_leaving_cb), NULL);
+ gaim_signal_connect(conv_handle, "chat-buddy-left",
+ plugin, GAIM_CALLBACK(chat_buddy_left_cb), NULL);
+ gaim_signal_connect(conv_handle, "chat-inviting-user",
+ plugin, GAIM_CALLBACK(chat_inviting_user_cb), NULL);
+ gaim_signal_connect(conv_handle, "chat-invited-user",
+ plugin, GAIM_CALLBACK(chat_invited_user_cb), NULL);
+ gaim_signal_connect(conv_handle, "chat-invited",
+ plugin, GAIM_CALLBACK(chat_invited_cb), NULL);
+ gaim_signal_connect(conv_handle, "chat-joined",
+ plugin, GAIM_CALLBACK(chat_joined_cb), NULL);
+ gaim_signal_connect(conv_handle, "chat-left",
+ plugin, GAIM_CALLBACK(chat_left_cb), NULL);
+ gaim_signal_connect(conv_handle, "chat-topic-changed",
+ plugin, GAIM_CALLBACK(chat_topic_changed_cb), NULL);
+
+ /* Ciphers signals */
+ gaim_signal_connect(ciphers_handle, "cipher-added",
+ plugin, GAIM_CALLBACK(cipher_added_cb), NULL);
+ gaim_signal_connect(ciphers_handle, "cipher-removed",
+ plugin, GAIM_CALLBACK(cipher_removed_cb), NULL);
+
+ /* Core signals */
+ gaim_signal_connect(core_handle, "quitting",
+ plugin, GAIM_CALLBACK(quitting_cb), NULL);
+
+ /* File transfer signals */
+ gaim_signal_connect(ft_handle, "file-recv-accept",
+ plugin, GAIM_CALLBACK(ft_recv_accept_cb), NULL);
+ gaim_signal_connect(ft_handle, "file-recv-start",
+ plugin, GAIM_CALLBACK(ft_recv_start_cb), NULL);
+ gaim_signal_connect(ft_handle, "file-recv-cancel",
+ plugin, GAIM_CALLBACK(ft_recv_cancel_cb), NULL);
+ gaim_signal_connect(ft_handle, "file-recv-complete",
+ plugin, GAIM_CALLBACK(ft_recv_complete_cb), NULL);
+ gaim_signal_connect(ft_handle, "file-send-accept",
+ plugin, GAIM_CALLBACK(ft_send_accept_cb), NULL);
+ gaim_signal_connect(ft_handle, "file-send-start",
+ plugin, GAIM_CALLBACK(ft_send_start_cb), NULL);
+ gaim_signal_connect(ft_handle, "file-send-cancel",
+ plugin, GAIM_CALLBACK(ft_send_cancel_cb), NULL);
+ gaim_signal_connect(ft_handle, "file-send-complete",
+ plugin, GAIM_CALLBACK(ft_send_complete_cb), NULL);
+
+ /* Sound signals */
+ gaim_signal_connect(sound_handle, "playing-sound-event", plugin,
+ GAIM_CALLBACK(sound_playing_event_cb), NULL);
+
+ return TRUE;
+}
+
+static GaimPluginInfo info =
+{
+ GAIM_PLUGIN_MAGIC,
+ GAIM_MAJOR_VERSION,
+ GAIM_MINOR_VERSION,
+ GAIM_PLUGIN_STANDARD, /**< type */
+ NULL, /**< ui_requirement */
+ 0, /**< flags */
+ NULL, /**< dependencies */
+ GAIM_PRIORITY_DEFAULT, /**< priority */
+
+ SIGNAL_TEST_PLUGIN_ID, /**< id */
+ N_("Signals Test"), /**< name */
+ VERSION, /**< version */
+ /** summary */
+ N_("Test to see that all signals are working properly."),
+ /** description */
+ N_("Test to see that all signals are working properly."),
+ "Christian Hammond <chipx86@gnupdate.org>", /**< author */
+ GAIM_WEBSITE, /**< homepage */
+
+ plugin_load, /**< load */
+ NULL, /**< unload */
+ NULL, /**< destroy */
+
+ NULL, /**< ui_info */
+ NULL, /**< extra_info */
+ NULL,
+ NULL
+};
+
+static void
+init_plugin(GaimPlugin *plugin)
+{
+}
+
+GAIM_INIT_PLUGIN(signalstest, init_plugin, info)
diff --git a/libpurple/plugins/simple.c b/libpurple/plugins/simple.c
new file mode 100644
index 0000000000..fb5fb7fa1c
--- /dev/null
+++ b/libpurple/plugins/simple.c
@@ -0,0 +1,61 @@
+#include "internal.h"
+#include "debug.h"
+#include "plugin.h"
+#include "version.h"
+
+/** Plugin id : type-author-name (to guarantee uniqueness) */
+#define SIMPLE_PLUGIN_ID "core-ewarmenhoven-simple"
+
+static gboolean
+plugin_load(GaimPlugin *plugin)
+{
+ gaim_debug(GAIM_DEBUG_INFO, "simple", "simple plugin loaded.\n");
+
+ return TRUE;
+}
+
+static gboolean
+plugin_unload(GaimPlugin *plugin)
+{
+ gaim_debug(GAIM_DEBUG_INFO, "simple", "simple plugin unloaded.\n");
+
+ return TRUE;
+}
+
+static GaimPluginInfo info =
+{
+ GAIM_PLUGIN_MAGIC,
+ GAIM_MAJOR_VERSION,
+ GAIM_MINOR_VERSION,
+ GAIM_PLUGIN_STANDARD, /**< type */
+ NULL, /**< ui_requirement */
+ 0, /**< flags */
+ NULL, /**< dependencies */
+ GAIM_PRIORITY_DEFAULT, /**< priority */
+
+ SIMPLE_PLUGIN_ID, /**< id */
+ N_("Simple Plugin"), /**< name */
+ VERSION, /**< version */
+ /** summary */
+ N_("Tests to see that most things are working."),
+ /** description */
+ N_("Tests to see that most things are working."),
+ "Eric Warmenhoven <eric@warmenhoven.org>", /**< author */
+ GAIM_WEBSITE, /**< homepage */
+
+ plugin_load, /**< load */
+ plugin_unload, /**< unload */
+ NULL, /**< destroy */
+
+ NULL, /**< ui_info */
+ NULL, /**< extra_info */
+ NULL,
+ NULL
+};
+
+static void
+init_plugin(GaimPlugin *plugin)
+{
+}
+
+GAIM_INIT_PLUGIN(simple, init_plugin, info)
diff --git a/libpurple/plugins/ssl/Makefile.am b/libpurple/plugins/ssl/Makefile.am
new file mode 100644
index 0000000000..dbdabdaf1f
--- /dev/null
+++ b/libpurple/plugins/ssl/Makefile.am
@@ -0,0 +1,35 @@
+EXTRA_DIST = \
+ Makefile.mingw
+
+plugindir = $(libdir)/gaim
+
+ssl_la_LDFLAGS = -module -avoid-version
+ssl_gnutls_la_LDFLAGS = -module -avoid-version
+ssl_nss_la_LDFLAGS = -module -avoid-version
+
+if PLUGINS
+
+plugin_LTLIBRARIES = \
+ ssl.la \
+ ssl-gnutls.la \
+ ssl-nss.la
+
+ssl_la_SOURCES = ssl.c
+ssl_gnutls_la_SOURCES = ssl-gnutls.c
+ssl_nss_la_SOURCES = ssl-nss.c
+
+ssl_la_LIBADD = $(GLIB_LIBS)
+ssl_gnutls_la_LIBADD = $(GLIB_LIBS) $(GNUTLS_LIBS)
+ssl_nss_la_LIBADD = $(GLIB_LIBS) $(NSS_LIBS)
+
+endif # PLUGINS
+
+AM_CPPFLAGS = \
+ -DDATADIR=\"$(datadir)\" \
+ -DLIBDIR=\"$(libdir)/gaim/\" \
+ -I$(top_srcdir)/libpurple \
+ $(DEBUG_CFLAGS) \
+ $(GLIB_CFLAGS) \
+ $(PLUGIN_CFLAGS) \
+ $(NSS_CFLAGS) \
+ $(GNUTLS_CFLAGS)
diff --git a/libpurple/plugins/ssl/Makefile.mingw b/libpurple/plugins/ssl/Makefile.mingw
new file mode 100644
index 0000000000..03023644e0
--- /dev/null
+++ b/libpurple/plugins/ssl/Makefile.mingw
@@ -0,0 +1,95 @@
+#
+# Makefile.mingw
+#
+# Description: Makefile for ssl plugin.
+#
+
+GAIM_TOP := ../../..
+include $(GAIM_TOP)/libgaim/win32/global.mak
+
+##
+## VARIABLE DEFINITIONS
+##
+TARGET = ssl
+TARGET_NSS = ssl-nss
+
+NEEDED_DLLS = \
+ $(NSS_TOP)/lib/freebl3.dll \
+ $(NSS_TOP)/lib/nss3.dll \
+ $(NSS_TOP)/lib/nssckbi.dll \
+ $(NSS_TOP)/lib/softokn3.dll \
+ $(NSS_TOP)/lib/ssl3.dll \
+ $(NSPR_TOP)/lib/nspr4.dll \
+ $(NSPR_TOP)/lib/plc4.dll \
+ $(NSPR_TOP)/lib/plds4.dll
+
+##
+## INCLUDE PATHS
+##
+INCLUDE_PATHS += -I. \
+ -I$(GTK_TOP)/include \
+ -I$(GTK_TOP)/include/glib-2.0 \
+ -I$(GTK_TOP)/lib/glib-2.0/include \
+ -I$(GAIM_LIB_TOP) \
+ -I$(GAIM_LIB_TOP)/win32 \
+ -I$(GAIM_TOP) \
+ -I$(NSS_TOP)/include \
+ -I$(NSPR_TOP)/include
+
+LIB_PATHS = -L$(GTK_TOP)/lib \
+ -L$(GAIM_LIB_TOP) \
+ -L$(NSS_TOP)/lib \
+ -L$(NSPR_TOP)/lib
+
+##
+## SOURCES, OBJECTS
+##
+C_SRC = ssl.c
+C_SRC_NSS = ssl-nss.c
+OBJECTS = $(C_SRC:%.c=%.o)
+OBJECTS_NSS = $(C_SRC_NSS:%.c=%.o)
+
+##
+## LIBRARIES
+##
+LIBS = \
+ -lglib-2.0 \
+ -lws2_32 \
+ -lintl \
+ -lgaim \
+ -lnss3 \
+ -lnspr4 \
+ -lssl3
+
+include $(GAIM_COMMON_RULES)
+
+##
+## TARGET DEFINITIONS
+##
+.PHONY: all install clean
+
+all: $(TARGET).dll $(TARGET_NSS).dll
+
+install: all $(GAIM_INSTALL_PLUGINS_DIR) $(GAIM_INSTALL_DIR)
+ cp $(TARGET).dll $(GAIM_INSTALL_PLUGINS_DIR)
+ cp $(TARGET_NSS).dll $(GAIM_INSTALL_PLUGINS_DIR)
+ cp $(NEEDED_DLLS) $(GAIM_INSTALL_DIR)
+
+$(OBJECTS) $(OBJECTS_NSS): $(GAIM_CONFIG_H)
+
+##
+## BUILD DLL
+##
+$(TARGET).dll: $(GAIM_LIBGAIM_DLL).a $(OBJECTS)
+ $(CC) -shared $(OBJECTS) $(LIB_PATHS) $(LIBS) $(DLL_LD_FLAGS) -o $(TARGET).dll
+
+$(TARGET_NSS).dll: $(GAIM_LIBGAIM_DLL) $(OBJECTS_NSS)
+ $(CC) -shared $(OBJECTS_NSS) $(LIB_PATHS) $(LIBS) $(DLL_LD_FLAGS) -o $(TARGET_NSS).dll
+
+##
+## CLEAN RULES
+##
+clean:
+ rm -f $(OBJECTS) $(OBJECTS_NSS) $(TARGET).dll $(TARGET_NSS).dll
+
+include $(GAIM_COMMON_TARGETS)
diff --git a/libpurple/plugins/ssl/ssl-gnutls.c b/libpurple/plugins/ssl/ssl-gnutls.c
new file mode 100644
index 0000000000..0d08dc33d7
--- /dev/null
+++ b/libpurple/plugins/ssl/ssl-gnutls.c
@@ -0,0 +1,264 @@
+/**
+ * @file ssl-gnutls.c GNUTLS SSL plugin.
+ *
+ * gaim
+ *
+ * Copyright (C) 2003 Christian Hammond <chipx86@gnupdate.org>
+ *
+ * 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 "debug.h"
+#include "plugin.h"
+#include "sslconn.h"
+#include "version.h"
+
+#define SSL_GNUTLS_PLUGIN_ID "ssl-gnutls"
+
+#ifdef HAVE_GNUTLS
+
+#include <gnutls/gnutls.h>
+
+typedef struct
+{
+ gnutls_session session;
+ guint handshake_handler;
+} GaimSslGnutlsData;
+
+#define GAIM_SSL_GNUTLS_DATA(gsc) ((GaimSslGnutlsData *)gsc->private_data)
+
+static gnutls_certificate_client_credentials xcred;
+
+static void
+ssl_gnutls_init_gnutls(void)
+{
+ gnutls_global_init();
+
+ gnutls_certificate_allocate_credentials(&xcred);
+ gnutls_certificate_set_x509_trust_file(xcred, "ca.pem",
+ GNUTLS_X509_FMT_PEM);
+}
+
+static gboolean
+ssl_gnutls_init(void)
+{
+ return TRUE;
+}
+
+static void
+ssl_gnutls_uninit(void)
+{
+ gnutls_global_deinit();
+
+ gnutls_certificate_free_credentials(xcred);
+}
+
+
+static void ssl_gnutls_handshake_cb(gpointer data, gint source,
+ GaimInputCondition cond)
+{
+ GaimSslConnection *gsc = data;
+ GaimSslGnutlsData *gnutls_data = GAIM_SSL_GNUTLS_DATA(gsc);
+ ssize_t ret;
+
+ gaim_debug_info("gnutls", "Handshaking\n");
+ ret = gnutls_handshake(gnutls_data->session);
+
+ if(ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED)
+ return;
+
+ gaim_input_remove(gnutls_data->handshake_handler);
+ gnutls_data->handshake_handler = 0;
+
+ if(ret != 0) {
+ gaim_debug_error("gnutls", "Handshake failed. Error %d\n", ret);
+
+ if(gsc->error_cb != NULL)
+ gsc->error_cb(gsc, GAIM_SSL_HANDSHAKE_FAILED,
+ gsc->connect_cb_data);
+
+ gaim_ssl_close(gsc);
+ } else {
+ gaim_debug_info("gnutls", "Handshake complete\n");
+
+ gsc->connect_cb(gsc->connect_cb_data, gsc, cond);
+ }
+
+}
+
+
+static void
+ssl_gnutls_connect(GaimSslConnection *gsc)
+{
+ GaimSslGnutlsData *gnutls_data;
+ static const int cert_type_priority[2] = { GNUTLS_CRT_X509, 0 };
+
+ gnutls_data = g_new0(GaimSslGnutlsData, 1);
+ gsc->private_data = gnutls_data;
+
+ gnutls_init(&gnutls_data->session, GNUTLS_CLIENT);
+ gnutls_set_default_priority(gnutls_data->session);
+
+ gnutls_certificate_type_set_priority(gnutls_data->session,
+ cert_type_priority);
+
+ gnutls_credentials_set(gnutls_data->session, GNUTLS_CRD_CERTIFICATE,
+ xcred);
+
+ gnutls_transport_set_ptr(gnutls_data->session, GINT_TO_POINTER(gsc->fd));
+
+ gnutls_data->handshake_handler = gaim_input_add(gsc->fd,
+ GAIM_INPUT_READ, ssl_gnutls_handshake_cb, gsc);
+
+ ssl_gnutls_handshake_cb(gsc, gsc->fd, GAIM_INPUT_READ);
+}
+
+static void
+ssl_gnutls_close(GaimSslConnection *gsc)
+{
+ GaimSslGnutlsData *gnutls_data = GAIM_SSL_GNUTLS_DATA(gsc);
+
+ if(!gnutls_data)
+ return;
+
+ if(gnutls_data->handshake_handler)
+ gaim_input_remove(gnutls_data->handshake_handler);
+
+ gnutls_bye(gnutls_data->session, GNUTLS_SHUT_RDWR);
+
+ gnutls_deinit(gnutls_data->session);
+
+ g_free(gnutls_data);
+ gsc->private_data = NULL;
+}
+
+static size_t
+ssl_gnutls_read(GaimSslConnection *gsc, void *data, size_t len)
+{
+ GaimSslGnutlsData *gnutls_data = GAIM_SSL_GNUTLS_DATA(gsc);
+ ssize_t s;
+
+ s = gnutls_record_recv(gnutls_data->session, data, len);
+
+ if(s == GNUTLS_E_AGAIN || s == GNUTLS_E_INTERRUPTED) {
+ s = -1;
+ errno = EAGAIN;
+ } else if(s < 0) {
+ gaim_debug_error("gnutls", "receive failed: %d\n", s);
+ s = 0;
+ }
+
+ return s;
+}
+
+static size_t
+ssl_gnutls_write(GaimSslConnection *gsc, const void *data, size_t len)
+{
+ GaimSslGnutlsData *gnutls_data = GAIM_SSL_GNUTLS_DATA(gsc);
+ ssize_t s = 0;
+
+ /* XXX: when will gnutls_data be NULL? */
+ if(gnutls_data)
+ s = gnutls_record_send(gnutls_data->session, data, len);
+
+ if(s == GNUTLS_E_AGAIN || s == GNUTLS_E_INTERRUPTED) {
+ s = -1;
+ errno = EAGAIN;
+ } else if(s < 0) {
+ gaim_debug_error("gnutls", "send failed: %d\n", s);
+ s = 0;
+ }
+
+ return s;
+}
+
+static GaimSslOps ssl_ops =
+{
+ ssl_gnutls_init,
+ ssl_gnutls_uninit,
+ ssl_gnutls_connect,
+ ssl_gnutls_close,
+ ssl_gnutls_read,
+ ssl_gnutls_write
+};
+
+#endif /* HAVE_GNUTLS */
+
+static gboolean
+plugin_load(GaimPlugin *plugin)
+{
+#ifdef HAVE_GNUTLS
+ if(!gaim_ssl_get_ops()) {
+ gaim_ssl_set_ops(&ssl_ops);
+ }
+
+ /* Init GNUTLS now so others can use it even if sslconn never does */
+ ssl_gnutls_init_gnutls();
+
+ return TRUE;
+#else
+ return FALSE;
+#endif
+}
+
+static gboolean
+plugin_unload(GaimPlugin *plugin)
+{
+#ifdef HAVE_GNUTLS
+ if(gaim_ssl_get_ops() == &ssl_ops) {
+ gaim_ssl_set_ops(NULL);
+ }
+#endif
+
+ return TRUE;
+}
+
+static GaimPluginInfo info =
+{
+ GAIM_PLUGIN_MAGIC,
+ GAIM_MAJOR_VERSION,
+ GAIM_MINOR_VERSION,
+ GAIM_PLUGIN_STANDARD, /**< type */
+ NULL, /**< ui_requirement */
+ GAIM_PLUGIN_FLAG_INVISIBLE, /**< flags */
+ NULL, /**< dependencies */
+ GAIM_PRIORITY_DEFAULT, /**< priority */
+
+ SSL_GNUTLS_PLUGIN_ID, /**< id */
+ N_("GNUTLS"), /**< name */
+ VERSION, /**< version */
+ /** summary */
+ N_("Provides SSL support through GNUTLS."),
+ /** description */
+ N_("Provides SSL support through GNUTLS."),
+ "Christian Hammond <chipx86@gnupdate.org>",
+ GAIM_WEBSITE, /**< homepage */
+
+ plugin_load, /**< load */
+ plugin_unload, /**< unload */
+ NULL, /**< destroy */
+
+ NULL, /**< ui_info */
+ NULL, /**< extra_info */
+ NULL, /**< prefs_info */
+ NULL /**< actions */
+};
+
+static void
+init_plugin(GaimPlugin *plugin)
+{
+}
+
+GAIM_INIT_PLUGIN(ssl_gnutls, init_plugin, info)
diff --git a/libpurple/plugins/ssl/ssl-nss.c b/libpurple/plugins/ssl/ssl-nss.c
new file mode 100644
index 0000000000..019b89db1a
--- /dev/null
+++ b/libpurple/plugins/ssl/ssl-nss.c
@@ -0,0 +1,433 @@
+/**
+ * @file ssl-nss.c Mozilla NSS SSL plugin.
+ *
+ * gaim
+ *
+ * Copyright (C) 2003 Christian Hammond <chipx86@gnupdate.org>
+ *
+ * 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 "debug.h"
+#include "plugin.h"
+#include "sslconn.h"
+#include "version.h"
+
+#define SSL_NSS_PLUGIN_ID "ssl-nss"
+
+#ifdef HAVE_NSS
+
+#undef HAVE_LONG_LONG /* Make Mozilla less angry. If angry, Mozilla SMASH! */
+
+#include <nspr.h>
+#include <private/pprio.h>
+#include <nss.h>
+#include <pk11func.h>
+#include <prio.h>
+#include <secerr.h>
+#include <secmod.h>
+#include <ssl.h>
+#include <sslerr.h>
+#include <sslproto.h>
+
+typedef struct
+{
+ PRFileDesc *fd;
+ PRFileDesc *in;
+ guint handshake_handler;
+
+} GaimSslNssData;
+
+#define GAIM_SSL_NSS_DATA(gsc) ((GaimSslNssData *)gsc->private_data)
+
+static const PRIOMethods *_nss_methods = NULL;
+static PRDescIdentity _identity;
+
+/* Thank you, Evolution */
+static void
+set_errno(int code)
+{
+ /* FIXME: this should handle more. */
+ switch (code) {
+ case PR_INVALID_ARGUMENT_ERROR:
+ errno = EINVAL;
+ break;
+ case PR_PENDING_INTERRUPT_ERROR:
+ errno = EINTR;
+ break;
+ case PR_IO_PENDING_ERROR:
+ errno = EAGAIN;
+ break;
+ case PR_WOULD_BLOCK_ERROR:
+ errno = EAGAIN;
+ /*errno = EWOULDBLOCK; */
+ break;
+ case PR_IN_PROGRESS_ERROR:
+ errno = EINPROGRESS;
+ break;
+ case PR_ALREADY_INITIATED_ERROR:
+ errno = EALREADY;
+ break;
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ errno = EHOSTUNREACH;
+ break;
+ case PR_CONNECT_REFUSED_ERROR:
+ errno = ECONNREFUSED;
+ break;
+ case PR_CONNECT_TIMEOUT_ERROR:
+ case PR_IO_TIMEOUT_ERROR:
+ errno = ETIMEDOUT;
+ break;
+ case PR_NOT_CONNECTED_ERROR:
+ errno = ENOTCONN;
+ break;
+ case PR_CONNECT_RESET_ERROR:
+ errno = ECONNRESET;
+ break;
+ case PR_IO_ERROR:
+ default:
+ errno = EIO;
+ break;
+ }
+}
+
+static void
+ssl_nss_init_nss(void)
+{
+ char *lib;
+ PR_Init(PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 1);
+ NSS_NoDB_Init(NULL);
+
+ /* TODO: Fix this so autoconf does the work trying to find this lib. */
+#ifndef _WIN32
+ lib = g_strdup(BR_LIBDIR("/libnssckbi.so"));
+#else
+ lib = g_strdup("nssckbi.dll");
+#endif
+ SECMOD_AddNewModule("Builtins", lib, 0, 0);
+ g_free(lib);
+ NSS_SetDomesticPolicy();
+
+ _identity = PR_GetUniqueIdentity("Gaim");
+ _nss_methods = PR_GetDefaultIOMethods();
+}
+
+static SECStatus
+ssl_auth_cert(void *arg, PRFileDesc *socket, PRBool checksig,
+ PRBool is_server)
+{
+ return SECSuccess;
+
+#if 0
+ CERTCertificate *cert;
+ void *pinArg;
+ SECStatus status;
+
+ cert = SSL_PeerCertificate(socket);
+ pinArg = SSL_RevealPinArg(socket);
+
+ status = CERT_VerifyCertNow((CERTCertDBHandle *)arg, cert, checksig,
+ certUsageSSLClient, pinArg);
+
+ if (status != SECSuccess) {
+ gaim_debug_error("nss", "CERT_VerifyCertNow failed\n");
+ CERT_DestroyCertificate(cert);
+ return status;
+ }
+
+ CERT_DestroyCertificate(cert);
+ return SECSuccess;
+#endif
+}
+
+static SECStatus
+ssl_bad_cert(void *arg, PRFileDesc *socket)
+{
+ SECStatus status = SECFailure;
+ PRErrorCode err;
+
+ if (arg == NULL)
+ return status;
+
+ *(PRErrorCode *)arg = err = PORT_GetError();
+
+ switch (err)
+ {
+ case SEC_ERROR_INVALID_AVA:
+ case SEC_ERROR_INVALID_TIME:
+ case SEC_ERROR_BAD_SIGNATURE:
+ case SEC_ERROR_EXPIRED_CERTIFICATE:
+ case SEC_ERROR_UNKNOWN_ISSUER:
+ case SEC_ERROR_UNTRUSTED_CERT:
+ case SEC_ERROR_CERT_VALID:
+ case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
+ case SEC_ERROR_CRL_EXPIRED:
+ case SEC_ERROR_CRL_BAD_SIGNATURE:
+ case SEC_ERROR_EXTENSION_VALUE_INVALID:
+ case SEC_ERROR_CA_CERT_INVALID:
+ case SEC_ERROR_CERT_USAGES_INVALID:
+ case SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION:
+ status = SECSuccess;
+ break;
+
+ default:
+ status = SECFailure;
+ break;
+ }
+
+ gaim_debug_error("nss", "Bad certificate: %d\n", err);
+
+ return status;
+}
+
+static gboolean
+ssl_nss_init(void)
+{
+ return TRUE;
+}
+
+static void
+ssl_nss_uninit(void)
+{
+ PR_Cleanup();
+
+ _nss_methods = NULL;
+}
+
+static void
+ssl_nss_handshake_cb(gpointer data, int fd, GaimInputCondition cond)
+{
+ GaimSslConnection *gsc = (GaimSslConnection *)data;
+ GaimSslNssData *nss_data = gsc->private_data;
+
+ /* I don't think this the best way to do this...
+ * It seems to work because it'll eventually use the cached value
+ */
+ if(SSL_ForceHandshake(nss_data->in) != SECSuccess) {
+ set_errno(PR_GetError());
+ if (errno == EAGAIN || errno == EWOULDBLOCK)
+ return;
+
+ gaim_debug_error("nss", "Handshake failed %d\n", PR_GetError());
+
+ if (gsc->error_cb != NULL)
+ gsc->error_cb(gsc, GAIM_SSL_HANDSHAKE_FAILED, gsc->connect_cb_data);
+
+ gaim_ssl_close(gsc);
+
+ return;
+ }
+
+ gaim_input_remove(nss_data->handshake_handler);
+ nss_data->handshake_handler = 0;
+
+ gsc->connect_cb(gsc->connect_cb_data, gsc, cond);
+}
+
+static void
+ssl_nss_connect(GaimSslConnection *gsc)
+{
+ GaimSslNssData *nss_data = g_new0(GaimSslNssData, 1);
+ PRSocketOptionData socket_opt;
+
+ gsc->private_data = nss_data;
+
+ nss_data->fd = PR_ImportTCPSocket(gsc->fd);
+
+ if (nss_data->fd == NULL)
+ {
+ gaim_debug_error("nss", "nss_data->fd == NULL!\n");
+
+ if (gsc->error_cb != NULL)
+ gsc->error_cb(gsc, GAIM_SSL_CONNECT_FAILED, gsc->connect_cb_data);
+
+ gaim_ssl_close((GaimSslConnection *)gsc);
+
+ return;
+ }
+
+ socket_opt.option = PR_SockOpt_Nonblocking;
+ socket_opt.value.non_blocking = PR_TRUE;
+
+ if (PR_SetSocketOption(nss_data->fd, &socket_opt) != PR_SUCCESS)
+ gaim_debug_warning("nss", "unable to set socket into non-blocking mode: %d\n", PR_GetError());
+
+ nss_data->in = SSL_ImportFD(NULL, nss_data->fd);
+
+ if (nss_data->in == NULL)
+ {
+ gaim_debug_error("nss", "nss_data->in == NUL!\n");
+
+ if (gsc->error_cb != NULL)
+ gsc->error_cb(gsc, GAIM_SSL_CONNECT_FAILED, gsc->connect_cb_data);
+
+ gaim_ssl_close((GaimSslConnection *)gsc);
+
+ return;
+ }
+
+ SSL_OptionSet(nss_data->in, SSL_SECURITY, PR_TRUE);
+ SSL_OptionSet(nss_data->in, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ SSL_AuthCertificateHook(nss_data->in,
+ (SSLAuthCertificate)ssl_auth_cert,
+ (void *)CERT_GetDefaultCertDB());
+ SSL_BadCertHook(nss_data->in, (SSLBadCertHandler)ssl_bad_cert, NULL);
+
+ if(gsc->host)
+ SSL_SetURL(nss_data->in, gsc->host);
+
+#if 0
+ /* This seems like it'd the be the correct way to implement the
+ nonblocking stuff, but it doesn't seem to work */
+ SSL_HandshakeCallback(nss_data->in,
+ (SSLHandshakeCallback) ssl_nss_handshake_cb, gsc);
+#endif
+ SSL_ResetHandshake(nss_data->in, PR_FALSE);
+
+ nss_data->handshake_handler = gaim_input_add(gsc->fd,
+ GAIM_INPUT_READ, ssl_nss_handshake_cb, gsc);
+
+ ssl_nss_handshake_cb(gsc, gsc->fd, GAIM_INPUT_READ);
+}
+
+static void
+ssl_nss_close(GaimSslConnection *gsc)
+{
+ GaimSslNssData *nss_data = GAIM_SSL_NSS_DATA(gsc);
+
+ if(!nss_data)
+ return;
+
+ if (nss_data->in) PR_Close(nss_data->in);
+ /* if (nss_data->fd) PR_Close(nss_data->fd); */
+
+ if (nss_data->handshake_handler)
+ gaim_input_remove(nss_data->handshake_handler);
+
+ g_free(nss_data);
+ gsc->private_data = NULL;
+}
+
+static size_t
+ssl_nss_read(GaimSslConnection *gsc, void *data, size_t len)
+{
+ ssize_t ret;
+ GaimSslNssData *nss_data = GAIM_SSL_NSS_DATA(gsc);
+
+ ret = PR_Read(nss_data->in, data, len);
+
+ if (ret == -1)
+ set_errno(PR_GetError());
+
+ return ret;
+}
+
+static size_t
+ssl_nss_write(GaimSslConnection *gsc, const void *data, size_t len)
+{
+ ssize_t ret;
+ GaimSslNssData *nss_data = GAIM_SSL_NSS_DATA(gsc);
+
+ if(!nss_data)
+ return 0;
+
+ ret = PR_Write(nss_data->in, data, len);
+
+ if (ret == -1)
+ set_errno(PR_GetError());
+
+ return ret;
+}
+
+static GaimSslOps ssl_ops =
+{
+ ssl_nss_init,
+ ssl_nss_uninit,
+ ssl_nss_connect,
+ ssl_nss_close,
+ ssl_nss_read,
+ ssl_nss_write
+};
+
+#endif /* HAVE_NSS */
+
+
+static gboolean
+plugin_load(GaimPlugin *plugin)
+{
+#ifdef HAVE_NSS
+ if (!gaim_ssl_get_ops()) {
+ gaim_ssl_set_ops(&ssl_ops);
+ }
+
+ /* Init NSS now, so others can use it even if sslconn never does */
+ ssl_nss_init_nss();
+
+ return TRUE;
+#else
+ return FALSE;
+#endif
+}
+
+static gboolean
+plugin_unload(GaimPlugin *plugin)
+{
+#ifdef HAVE_NSS
+ if (gaim_ssl_get_ops() == &ssl_ops) {
+ gaim_ssl_set_ops(NULL);
+ }
+#endif
+
+ return TRUE;
+}
+
+static GaimPluginInfo info =
+{
+ GAIM_PLUGIN_MAGIC,
+ GAIM_MAJOR_VERSION,
+ GAIM_MINOR_VERSION,
+ GAIM_PLUGIN_STANDARD, /**< type */
+ NULL, /**< ui_requirement */
+ GAIM_PLUGIN_FLAG_INVISIBLE, /**< flags */
+ NULL, /**< dependencies */
+ GAIM_PRIORITY_DEFAULT, /**< priority */
+
+ SSL_NSS_PLUGIN_ID, /**< id */
+ N_("NSS"), /**< name */
+ VERSION, /**< version */
+ /** summary */
+ N_("Provides SSL support through Mozilla NSS."),
+ /** description */
+ N_("Provides SSL support through Mozilla NSS."),
+ "Christian Hammond <chipx86@gnupdate.org>",
+ GAIM_WEBSITE, /**< homepage */
+
+ plugin_load, /**< load */
+ plugin_unload, /**< unload */
+ NULL, /**< destroy */
+
+ NULL, /**< ui_info */
+ NULL, /**< extra_info */
+ NULL, /**< prefs_info */
+ NULL /**< actions */
+};
+
+static void
+init_plugin(GaimPlugin *plugin)
+{
+}
+
+GAIM_INIT_PLUGIN(ssl_nss, init_plugin, info)
diff --git a/libpurple/plugins/ssl/ssl.c b/libpurple/plugins/ssl/ssl.c
new file mode 100644
index 0000000000..740ce5f562
--- /dev/null
+++ b/libpurple/plugins/ssl/ssl.c
@@ -0,0 +1,118 @@
+/**
+ * @file ssl.c Main SSL plugin
+ *
+ * gaim
+ *
+ * Copyright (C) 2003 Christian Hammond <chipx86@gnupdate.org>
+ *
+ * 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 "debug.h"
+#include "plugin.h"
+#include "sslconn.h"
+#include "version.h"
+
+#define SSL_PLUGIN_ID "core-ssl"
+
+static GaimPlugin *ssl_plugin = NULL;
+
+static gboolean
+probe_ssl_plugins(GaimPlugin *my_plugin)
+{
+ GaimPlugin *plugin;
+ GList *l;
+
+ ssl_plugin = NULL;
+
+ for (l = gaim_plugins_get_all(); l != NULL; l = l->next)
+ {
+ plugin = (GaimPlugin *)l->data;
+
+ if (plugin == my_plugin)
+ continue;
+
+ if (plugin->info != NULL && plugin->info->id != NULL &&
+ strncmp(plugin->info->id, "ssl-", 4) == 0)
+ {
+ if (gaim_plugin_is_loaded(plugin) || gaim_plugin_load(plugin))
+ {
+ ssl_plugin = plugin;
+
+ break;
+ }
+ }
+ }
+
+ return (ssl_plugin != NULL);
+}
+
+static gboolean
+plugin_load(GaimPlugin *plugin)
+{
+ return probe_ssl_plugins(plugin);
+}
+
+static gboolean
+plugin_unload(GaimPlugin *plugin)
+{
+ if (ssl_plugin != NULL &&
+ g_list_find(gaim_plugins_get_loaded(), ssl_plugin) != NULL)
+ {
+ gaim_plugin_unload(ssl_plugin);
+ }
+
+ ssl_plugin = NULL;
+
+ return TRUE;
+}
+
+static GaimPluginInfo info =
+{
+ GAIM_PLUGIN_MAGIC,
+ GAIM_MAJOR_VERSION,
+ GAIM_MINOR_VERSION,
+ GAIM_PLUGIN_STANDARD, /**< type */
+ NULL, /**< ui_requirement */
+ GAIM_PLUGIN_FLAG_INVISIBLE, /**< flags */
+ NULL, /**< dependencies */
+ GAIM_PRIORITY_DEFAULT, /**< priority */
+
+ SSL_PLUGIN_ID, /**< id */
+ N_("SSL"), /**< name */
+ VERSION, /**< version */
+ /** summary */
+ N_("Provides a wrapper around SSL support libraries."),
+ /** description */
+ N_("Provides a wrapper around SSL support libraries."),
+ "Christian Hammond <chipx86@gnupdate.org>",
+ GAIM_WEBSITE, /**< homepage */
+
+ plugin_load, /**< load */
+ plugin_unload, /**< unload */
+ NULL, /**< destroy */
+
+ NULL, /**< ui_info */
+ NULL, /**< extra_info */
+ NULL,
+ NULL
+};
+
+static void
+init_plugin(GaimPlugin *plugin)
+{
+}
+
+GAIM_INIT_PLUGIN(ssl, init_plugin, info)
diff --git a/libpurple/plugins/statenotify.c b/libpurple/plugins/statenotify.c
new file mode 100644
index 0000000000..fcaf67c540
--- /dev/null
+++ b/libpurple/plugins/statenotify.c
@@ -0,0 +1,172 @@
+#include "internal.h"
+
+#include "blist.h"
+#include "conversation.h"
+#include "debug.h"
+#include "signals.h"
+#include "version.h"
+
+#include "plugin.h"
+#include "pluginpref.h"
+#include "prefs.h"
+
+#define STATENOTIFY_PLUGIN_ID "core-statenotify"
+
+static void
+write_status(GaimBuddy *buddy, const char *message)
+{
+ GaimConversation *conv;
+ const char *who;
+ char buf[256];
+ char *escaped;
+
+ conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM,
+ buddy->name, buddy->account);
+
+ if (conv == NULL)
+ return;
+ g_return_if_fail(conv->type == GAIM_CONV_TYPE_IM);
+
+ who = gaim_buddy_get_alias(buddy);
+ escaped = g_markup_escape_text(who, -1);
+
+ g_snprintf(buf, sizeof(buf), message, escaped);
+ g_free(escaped);
+
+ gaim_conv_im_write(conv->u.im, NULL, buf, GAIM_MESSAGE_SYSTEM | GAIM_MESSAGE_ACTIVE_ONLY, time(NULL));
+}
+
+static void
+buddy_status_changed_cb(GaimBuddy *buddy, GaimStatus *old_status,
+ GaimStatus *status, void *data)
+{
+ gboolean available, old_available;
+
+ available = gaim_status_is_available(status);
+ old_available = gaim_status_is_available(old_status);
+
+ if (gaim_prefs_get_bool("/plugins/core/statenotify/notify_away")) {
+ if (available && !old_available)
+ write_status(buddy, _("%s is no longer away."));
+ else if (!available && old_available)
+ write_status(buddy, _("%s has gone away."));
+ }
+}
+
+static void
+buddy_idle_changed_cb(GaimBuddy *buddy, gboolean old_idle, gboolean idle,
+ void *data)
+{
+ if (gaim_prefs_get_bool("/plugins/core/statenotify/notify_idle")) {
+ if (idle) {
+ write_status(buddy, _("%s has become idle."));
+ } else {
+ write_status(buddy, _("%s is no longer idle."));
+ }
+ }
+}
+
+static void
+buddy_signon_cb(GaimBuddy *buddy, void *data)
+{
+ if (gaim_prefs_get_bool("/plugins/core/statenotify/notify_signon"))
+ write_status(buddy, _("%s has signed on."));
+}
+
+static void
+buddy_signoff_cb(GaimBuddy *buddy, void *data)
+{
+ if (gaim_prefs_get_bool("/plugins/core/statenotify/notify_signon"))
+ write_status(buddy, _("%s has signed off."));
+}
+
+static GaimPluginPrefFrame *
+get_plugin_pref_frame(GaimPlugin *plugin)
+{
+ GaimPluginPrefFrame *frame;
+ GaimPluginPref *ppref;
+
+ frame = gaim_plugin_pref_frame_new();
+
+ ppref = gaim_plugin_pref_new_with_label(_("Notify When"));
+ gaim_plugin_pref_frame_add(frame, ppref);
+
+ ppref = gaim_plugin_pref_new_with_name_and_label("/plugins/core/statenotify/notify_away", _("Buddy Goes _Away"));
+ gaim_plugin_pref_frame_add(frame, ppref);
+
+ ppref = gaim_plugin_pref_new_with_name_and_label("/plugins/core/statenotify/notify_idle", _("Buddy Goes _Idle"));
+ gaim_plugin_pref_frame_add(frame, ppref);
+
+ ppref = gaim_plugin_pref_new_with_name_and_label("/plugins/core/statenotify/notify_signon", _("Buddy _Signs On/Off"));
+ gaim_plugin_pref_frame_add(frame, ppref);
+
+ return frame;
+}
+
+static gboolean
+plugin_load(GaimPlugin *plugin)
+{
+ void *blist_handle = gaim_blist_get_handle();
+
+ gaim_signal_connect(blist_handle, "buddy-status-changed", plugin,
+ GAIM_CALLBACK(buddy_status_changed_cb), NULL);
+ gaim_signal_connect(blist_handle, "buddy-idle-changed", plugin,
+ GAIM_CALLBACK(buddy_idle_changed_cb), NULL);
+ gaim_signal_connect(blist_handle, "buddy-signed-on", plugin,
+ GAIM_CALLBACK(buddy_signon_cb), NULL);
+ gaim_signal_connect(blist_handle, "buddy-signed-off", plugin,
+ GAIM_CALLBACK(buddy_signoff_cb), NULL);
+
+ return TRUE;
+}
+
+static GaimPluginUiInfo prefs_info =
+{
+ get_plugin_pref_frame,
+ 0, /* page_num (Reserved) */
+ NULL /* frame (Reserved) */
+};
+
+static GaimPluginInfo info =
+{
+ GAIM_PLUGIN_MAGIC,
+ GAIM_MAJOR_VERSION,
+ GAIM_MINOR_VERSION,
+ GAIM_PLUGIN_STANDARD, /**< type */
+ NULL, /**< ui_requirement */
+ 0, /**< flags */
+ NULL, /**< dependencies */
+ GAIM_PRIORITY_DEFAULT, /**< priority */
+
+ STATENOTIFY_PLUGIN_ID, /**< id */
+ N_("Buddy State Notification"), /**< name */
+ VERSION, /**< version */
+ /** summary */
+ N_("Notifies in a conversation window when a buddy goes or returns from "
+ "away or idle."),
+ /** description */
+ N_("Notifies in a conversation window when a buddy goes or returns from "
+ "away or idle."),
+ "Christian Hammond <chipx86@gnupdate.org>", /**< author */
+ GAIM_WEBSITE, /**< homepage */
+
+ plugin_load, /**< load */
+ NULL, /**< unload */
+ NULL, /**< destroy */
+
+ NULL, /**< ui_info */
+ NULL, /**< extra_info */
+ &prefs_info, /**< prefs_info */
+ NULL
+};
+
+static void
+init_plugin(GaimPlugin *plugin)
+{
+ gaim_prefs_add_none("/plugins/core/statenotify");
+ gaim_prefs_add_bool("/plugins/core/statenotify/notify_away", TRUE);
+ gaim_prefs_add_bool("/plugins/core/statenotify/notify_idle", TRUE);
+ gaim_prefs_add_bool("/plugins/core/statenotify/notify_signon", TRUE);
+}
+
+GAIM_INIT_PLUGIN(statenotify, init_plugin, info)
diff --git a/libpurple/plugins/tcl/Makefile.am b/libpurple/plugins/tcl/Makefile.am
new file mode 100644
index 0000000000..b2551b4e11
--- /dev/null
+++ b/libpurple/plugins/tcl/Makefile.am
@@ -0,0 +1,22 @@
+plugindir = $(libdir)/gaim
+
+tcl_la_LDFLAGS = -module -avoid-version
+
+plugin_LTLIBRARIES = tcl.la
+
+tcl_la_SOURCES = tcl.c tcl_glib.c tcl_glib.h tcl_cmds.c tcl_signals.c tcl_gaim.h \
+ tcl_ref.c tcl_cmd.c
+
+tcl_la_LIBADD = $(GLIB_LIBS) $(TCL_LIBS) $(TK_LIBS)
+
+EXTRA_DIST = signal-test.tcl Makefile.mingw
+
+AM_CPPFLAGS = \
+ -DVERSION=\"$(VERSION)\" \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/libpurple \
+ $(DEBUG_CFLAGS) \
+ $(GLIB_CFLAGS) \
+ $(PLUGIN_CFLAGS) \
+ $(TK_CFLAGS) \
+ $(TCL_CFLAGS)
diff --git a/libpurple/plugins/tcl/Makefile.mingw b/libpurple/plugins/tcl/Makefile.mingw
new file mode 100644
index 0000000000..11d9846ed0
--- /dev/null
+++ b/libpurple/plugins/tcl/Makefile.mingw
@@ -0,0 +1,77 @@
+#
+# Makefile.mingw
+#
+# Description: Makefile for tcl plugin loader plugin.
+#
+
+GAIM_TOP := ../../..
+include $(GAIM_TOP)/libgaim/win32/global.mak
+
+TARGET = tcl
+TCL_INC_DIR := $(TCL_LIB_TOP)/include
+DEFINES += -DHAVE_TK -DUSE_TCL_STUBS -DUSE_TK_STUBS
+
+##
+## INCLUDE PATHS
+##
+INCLUDE_PATHS += -I. \
+ -I$(GAIM_TOP) \
+ -I$(GAIM_LIB_TOP) \
+ -I$(GAIM_LIB_TOP)/win32 \
+ -I$(GTK_TOP)/include \
+ -I$(GTK_TOP)/include/glib-2.0 \
+ -I$(GTK_TOP)/lib/glib-2.0/include \
+ -I$(TCL_INC_DIR)
+
+LIB_PATHS += -L$(GTK_TOP)/lib \
+ -L$(GAIM_LIB_TOP) \
+ -L$(TCL_LIB_TOP)
+
+##
+## SOURCES, OBJECTS
+##
+C_SRC = tcl.c \
+ tcl_cmd.c \
+ tcl_cmds.c \
+ tcl_glib.c \
+ tcl_ref.c \
+ tcl_signals.c
+
+OBJECTS = $(C_SRC:%.c=%.o)
+
+##
+## LIBRARIES
+##
+LIBS = \
+ -lglib-2.0 \
+ -lws2_32 \
+ -lintl \
+ -lgaim \
+ -ltclstub84 \
+ -ltkstub84
+
+include $(GAIM_COMMON_RULES)
+
+##
+## TARGET DEFINITIONS
+##
+.PHONY: all install clean
+
+all: $(TARGET).dll
+
+install: all $(GAIM_INSTALL_PLUGINS_DIR)
+ cp $(TARGET).dll $(GAIM_INSTALL_PLUGINS_DIR)
+
+$(OBJECTS): $(GAIM_CONFIG_H)
+
+$(TARGET).dll: $(GAIM_LIBGAIM_DLL).a $(OBJECTS)
+ $(CC) -shared $(OBJECTS) $(LIB_PATHS) $(LIBS) $(DLL_LD_FLAGS) -o $(TARGET).dll
+
+##
+## CLEAN RULES
+##
+clean:
+ rm -rf $(OBJECTS)
+ rm -rf $(TARGET).dll
+
+include $(GAIM_COMMON_TARGETS)
diff --git a/libpurple/plugins/tcl/signal-test.tcl b/libpurple/plugins/tcl/signal-test.tcl
new file mode 100644
index 0000000000..de6ddaf6fe
--- /dev/null
+++ b/libpurple/plugins/tcl/signal-test.tcl
@@ -0,0 +1,110 @@
+gaim::signal connect [gaim::account handle] account-away { account state message } {
+ gaim::debug -info "tcl signal" "account-away [gaim::account username $account] \"$state\" \"$message\""
+}
+
+gaim::signal connect [gaim::account handle] account-connecting { account } {
+ gaim::debug -info "tcl signal" "account-connecting [gaim::account username $account]"
+}
+
+gaim::signal connect [gaim::account handle] account-set-info { account info } {
+ gaim::debug -info "tcl signal" "account-set-info [gaim::account username $account] $info"
+}
+
+gaim::signal connect [gaim::account handle] account-setting-info { account info } {
+ gaim::debug -info "tcl signal" "account-set-info [gaim::account username $account] $info"
+}
+
+gaim::signal connect [gaim::buddy handle] buddy-away { buddy } {
+ gaim::debug -info "tcl signal" "buddy-away [gaim::account username [lindex $buddy 2]] [lindex $buddy 1]"
+}
+
+gaim::signal connect [gaim::buddy handle] buddy-back { buddy } {
+ gaim::debug -info "tcl signal" "buddy-back [gaim::account username [lindex $buddy 2]] [lindex $buddy 1]"
+}
+
+gaim::signal connect [gaim::buddy handle] buddy-idle { buddy } {
+ gaim::debug -info "tcl signal" "buddy-idle [gaim::account username [lindex $buddy 2]] [lindex $buddy 1]"
+}
+
+gaim::signal connect [gaim::buddy handle] buddy-unidle { buddy } {
+ gaim::debug -info "tcl signal" "buddy-unidle [gaim::account username [lindex $buddy 2]] [lindex $buddy 1]"
+}
+
+gaim::signal connect [gaim::buddy handle] buddy-signed-on { buddy } {
+ gaim::debug -info "tcl signal" "buddy-signed-on [gaim::account username [lindex $buddy 2]] [lindex $buddy 1]"
+}
+
+gaim::signal connect [gaim::buddy handle] buddy-signed-off { buddy } {
+ gaim::debug -info "tcl signal" "buddy-signed-off [gaim::account username [lindex $buddy 2]] [lindex $buddy 1]"
+}
+
+gaim::signal connect [gaim::core handle] quitting {} {
+ gaim::debug -info "tcl signal" "quitting"
+}
+
+gaim::signal connect [gaim::conversation handle] receiving-chat-msg { account who what id flags } {
+ gaim::debug -info "tcl signal" "receiving-chat-msg [gaim::account username $account] $id $flags $who \"$what\""
+ return 0
+}
+
+gaim::signal connect [gaim::conversation handle] receiving-im-msg { account who what id flags } {
+ gaim::debug -info "tcl signal" "receiving-im-msg [gaim::account username $account] $id $flags $who \"$what\""
+ return 0
+}
+
+gaim::signal connect [gaim::conversation handle] received-chat-msg { account who what id flags } {
+ gaim::debug -info "tcl signal" "received-chat-msg [gaim::account username $account] $id $flags $who \"$what\""
+}
+
+gaim::signal connect [gaim::conversation handle] received-im-msg { account who what id flags } {
+ gaim::debug -info "tcl signal" "received-im-msg [gaim::account username $account] $id $flags $who \"$what\""
+}
+
+gaim::signal connect [gaim::conversation handle] sending-chat-msg { account what id } {
+ gaim::debug -info "tcl signal" "sending-chat-msg [gaim::account username $account] $id \"$what\""
+ return 0
+}
+
+gaim::signal connect [gaim::conversation handle] sending-im-msg { account who what } {
+ gaim::debug -info "tcl signal" "sending-im-msg [gaim::account username $account] $who \"$what\""
+ return 0
+}
+
+gaim::signal connect [gaim::conversation handle] sent-chat-msg { account id what } {
+ gaim::debug -info "tcl signal" "sent-chat-msg [gaim::account username $account] $id \"$what\""
+}
+
+gaim::signal connect [gaim::conversation handle] sent-im-msg { account who what } {
+ gaim::debug -info "tcl signal" "sent-im-msg [gaim::account username $account] $who \"$what\""
+}
+
+gaim::signal connect [gaim::connection handle] signed-on { gc } {
+ gaim::debug -info "tcl signal" "signed-on [gaim::account username [gaim::connection account $gc]]"
+}
+
+gaim::signal connect [gaim::connection handle] signed-off { gc } {
+ gaim::debug -info "tcl signal" "signed-off [gaim::account username [gaim::connection account $gc]]"
+}
+
+gaim::signal connect [gaim::connection handle] signing-on { gc } {
+ gaim::debug -info "tcl signal" "signing-on [gaim::account username [gaim::connection account $gc]]"
+}
+
+if { 0 } {
+gaim::signal connect signing-off {
+ gaim::debug -info "tcl signal" "signing-off [gaim::account username [gaim::connection account $event::gc]]"
+}
+
+gaim::signal connect update-idle {
+ gaim::debug -info "tcl signal" "update-idle"
+}
+}
+
+proc plugin_init { } {
+ list "Tcl Signal Test" \
+ "$gaim::version" \
+ "Tests Tcl signal handlers" \
+ "Debugs a ridiculous amount of signal information." \
+ "Ethan Blanton <eblanton@cs.purdue.edu>" \
+ "http://gaim.sourceforge.net/"
+}
diff --git a/libpurple/plugins/tcl/tcl.c b/libpurple/plugins/tcl/tcl.c
new file mode 100644
index 0000000000..67260b1f3a
--- /dev/null
+++ b/libpurple/plugins/tcl/tcl.c
@@ -0,0 +1,527 @@
+/**
+ * @file tcl.c Gaim Tcl plugin bindings
+ *
+ * gaim
+ *
+ * Copyright (C) 2003 Ethan Blanton <eblanton@cs.purdue.edu>
+ *
+ * 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 "config.h"
+
+#include <tcl.h>
+
+#ifdef HAVE_TK
+#include <tk.h>
+#endif
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "tcl_glib.h"
+#include "tcl_gaim.h"
+
+#include "internal.h"
+#include "connection.h"
+#include "plugin.h"
+#include "signals.h"
+#include "debug.h"
+#include "util.h"
+#include "version.h"
+
+struct tcl_plugin_data {
+ GaimPlugin *plugin;
+ Tcl_Interp *interp;
+};
+
+GaimStringref *GaimTclRefAccount;
+GaimStringref *GaimTclRefConnection;
+GaimStringref *GaimTclRefConversation;
+GaimStringref *GaimTclRefPointer;
+GaimStringref *GaimTclRefPlugin;
+GaimStringref *GaimTclRefPresence;
+GaimStringref *GaimTclRefStatus;
+GaimStringref *GaimTclRefStatusAttr;
+GaimStringref *GaimTclRefStatusType;
+GaimStringref *GaimTclRefXfer;
+
+static GHashTable *tcl_plugins = NULL;
+
+GaimPlugin *_tcl_plugin;
+
+static gboolean tcl_loaded = FALSE;
+
+GaimPlugin *tcl_interp_get_plugin(Tcl_Interp *interp)
+{
+ struct tcl_plugin_data *data;
+
+ if (tcl_plugins == NULL)
+ return NULL;
+
+ data = g_hash_table_lookup(tcl_plugins, (gpointer)interp);
+ return data != NULL ? data->plugin : NULL;
+}
+
+static int tcl_init_interp(Tcl_Interp *interp)
+{
+ char *rcfile;
+ char init[] =
+ "namespace eval ::gaim {\n"
+ " namespace export account buddy connection conversation\n"
+ " namespace export core debug notify prefs send_im\n"
+ " namespace export signal unload\n"
+ " namespace eval _callback { }\n"
+ "\n"
+ " proc conv_send { account who text } {\n"
+ " set gc [gaim::account connection $account]\n"
+ " set convo [gaim::conversation new $account $who]\n"
+ " set myalias [gaim::account alias $account]\n"
+ "\n"
+ " if {![string length $myalias]} {\n"
+ " set myalias [gaim::account username $account]\n"
+ " }\n"
+ "\n"
+ " gaim::send_im $gc $who $text\n"
+ " gaim::conversation write $convo send $myalias $text\n"
+ " }\n"
+ "}\n"
+ "\n"
+ "proc bgerror { message } {\n"
+ " global errorInfo\n"
+ " gaim::notify -error \"Tcl Error\" \"Tcl Error: $message\" \"$errorInfo\"\n"
+ "}\n";
+
+ if (Tcl_EvalEx(interp, init, -1, TCL_EVAL_GLOBAL) != TCL_OK) {
+ return 1;
+ }
+
+ Tcl_SetVar(interp, "argc", "0", TCL_GLOBAL_ONLY);
+ Tcl_SetVar(interp, "argv0", "gaim", TCL_GLOBAL_ONLY);
+ Tcl_SetVar(interp, "tcl_interactive", "0", TCL_GLOBAL_ONLY);
+ rcfile = g_strdup_printf("%s" G_DIR_SEPARATOR_S "tclrc", gaim_user_dir());
+ Tcl_SetVar(interp, "tcl_rcFileName", rcfile, TCL_GLOBAL_ONLY);
+ g_free(rcfile);
+
+ Tcl_SetVar(interp, "::gaim::version", VERSION, TCL_GLOBAL_ONLY);
+ Tcl_SetVar(interp, "::gaim::user_dir", gaim_user_dir(), TCL_GLOBAL_ONLY);
+#ifdef HAVE_TK
+ Tcl_SetVar(interp, "::gaim::tk_available", "1", TCL_GLOBAL_ONLY);
+#else
+ Tcl_SetVar(interp, "::gaim::tk_available", "0", TCL_GLOBAL_ONLY);
+#endif /* HAVE_TK */
+
+ Tcl_CreateObjCommand(interp, "::gaim::account", tcl_cmd_account, (ClientData)NULL, NULL);
+ Tcl_CreateObjCommand(interp, "::gaim::buddy", tcl_cmd_buddy, (ClientData)NULL, NULL);
+ Tcl_CreateObjCommand(interp, "::gaim::cmd", tcl_cmd_cmd, (ClientData)NULL, NULL);
+ Tcl_CreateObjCommand(interp, "::gaim::connection", tcl_cmd_connection, (ClientData)NULL, NULL);
+ Tcl_CreateObjCommand(interp, "::gaim::conversation", tcl_cmd_conversation, (ClientData)NULL, NULL);
+ Tcl_CreateObjCommand(interp, "::gaim::core", tcl_cmd_core, (ClientData)NULL, NULL);
+ Tcl_CreateObjCommand(interp, "::gaim::debug", tcl_cmd_debug, (ClientData)NULL, NULL);
+ Tcl_CreateObjCommand(interp, "::gaim::notify", tcl_cmd_notify, (ClientData)NULL, NULL);
+ Tcl_CreateObjCommand(interp, "::gaim::prefs", tcl_cmd_prefs, (ClientData)NULL, NULL);
+ Tcl_CreateObjCommand(interp, "::gaim::presence", tcl_cmd_presence, (ClientData)NULL, NULL);
+ Tcl_CreateObjCommand(interp, "::gaim::send_im", tcl_cmd_send_im, (ClientData)NULL, NULL);
+ Tcl_CreateObjCommand(interp, "::gaim::signal", tcl_cmd_signal, (ClientData)NULL, NULL);
+ Tcl_CreateObjCommand(interp, "::gaim::status", tcl_cmd_status, (ClientData)NULL, NULL);
+ Tcl_CreateObjCommand(interp, "::gaim::status_attr", tcl_cmd_status_attr, (ClientData)NULL, NULL);
+ Tcl_CreateObjCommand(interp, "::gaim::status_type", tcl_cmd_status_type, (ClientData)NULL, NULL);
+ Tcl_CreateObjCommand(interp, "::gaim::unload", tcl_cmd_unload, (ClientData)NULL, NULL);
+
+ return 0;
+}
+
+static Tcl_Interp *tcl_create_interp()
+{
+ Tcl_Interp *interp;
+
+ interp = Tcl_CreateInterp();
+ if (Tcl_Init(interp) == TCL_ERROR) {
+ Tcl_DeleteInterp(interp);
+ return NULL;
+ }
+
+ if (tcl_init_interp(interp)) {
+ Tcl_DeleteInterp(interp);
+ return NULL;
+ }
+ Tcl_StaticPackage(interp, "gaim", tcl_init_interp, NULL);
+
+ return interp;
+}
+
+static gboolean tcl_probe_plugin(GaimPlugin *plugin)
+{
+ GaimPluginInfo *info;
+ Tcl_Interp *interp;
+ Tcl_Parse parse;
+ Tcl_Obj *result, **listitems;
+ struct stat st;
+ FILE *fp;
+ char *buf, *cur;
+ const char *next;
+ int len, found = 0, err = 0, nelems;
+ gboolean status = FALSE;
+ if ((fp = g_fopen(plugin->path, "r")) == NULL)
+ return FALSE;
+ if (fstat(fileno(fp), &st)) {
+ fclose(fp);
+ return FALSE;
+ }
+ len = st.st_size;
+
+ buf = g_malloc(len + 1);
+
+ cur = buf;
+ while (fgets(cur, GPOINTER_TO_INT(buf) - (buf - cur), fp)) {
+ cur += strlen(cur);
+ if (feof(fp))
+ break;
+ }
+
+ if (ferror(fp)) {
+ gaim_debug(GAIM_DEBUG_ERROR, "tcl", "error reading %s (%s)\n", plugin->path, strerror(errno));
+ g_free(buf);
+ fclose(fp);
+ return FALSE;
+ }
+
+ fclose(fp);
+
+ if ((interp = tcl_create_interp()) == NULL) {
+ return FALSE;
+ }
+
+ next = buf;
+ do {
+ if (Tcl_ParseCommand(interp, next, len, 0, &parse) == TCL_ERROR) {
+ gaim_debug(GAIM_DEBUG_ERROR, "tcl", "parse error in %s: %s\n", plugin->path,
+ Tcl_GetString(Tcl_GetObjResult(interp)));
+ err = 1;
+ break;
+ }
+ if (parse.tokenPtr[0].type == TCL_TOKEN_SIMPLE_WORD
+ && !strncmp(parse.tokenPtr[0].start, "proc", parse.tokenPtr[0].size)) {
+ if (!strncmp(parse.tokenPtr[2].start, "plugin_init", parse.tokenPtr[2].size)) {
+ if (Tcl_EvalEx(interp, parse.commandStart, parse.commandSize, TCL_EVAL_GLOBAL) != TCL_OK) {
+ Tcl_FreeParse(&parse);
+ break;
+ }
+ found = 1;
+ /* We'll continue parsing the file, just in case */
+ }
+ }
+ len -= (parse.commandStart + parse.commandSize) - next;
+ next = parse.commandStart + parse.commandSize;
+ Tcl_FreeParse(&parse);
+ } while (len);
+
+ if (found && !err) {
+ if (Tcl_EvalEx(interp, "plugin_init", -1, TCL_EVAL_GLOBAL) == TCL_OK) {
+ result = Tcl_GetObjResult(interp);
+ if (Tcl_ListObjGetElements(interp, result, &nelems, &listitems) == TCL_OK) {
+ if ((nelems == 6) || (nelems == 7)) {
+ info = g_new0(GaimPluginInfo, 1);
+
+ info->magic = GAIM_PLUGIN_MAGIC;
+ info->major_version = GAIM_MAJOR_VERSION;
+ info->minor_version = GAIM_MINOR_VERSION;
+ info->type = GAIM_PLUGIN_STANDARD;
+ info->dependencies = g_list_append(info->dependencies, "core-tcl");
+
+ info->name = g_strdup(Tcl_GetString(listitems[0]));
+ info->version = g_strdup(Tcl_GetString(listitems[1]));
+ info->summary = g_strdup(Tcl_GetString(listitems[2]));
+ info->description = g_strdup(Tcl_GetString(listitems[3]));
+ info->author = g_strdup(Tcl_GetString(listitems[4]));
+ info->homepage = g_strdup(Tcl_GetString(listitems[5]));
+
+ if (nelems == 6)
+ info->id = g_strdup_printf("tcl-%s", Tcl_GetString(listitems[0]));
+ else if (nelems == 7)
+ info->id = g_strdup_printf("tcl-%s", Tcl_GetString(listitems[6]));
+
+ plugin->info = info;
+
+ if (gaim_plugin_register(plugin))
+ status = TRUE;
+ }
+ }
+ }
+ }
+
+ Tcl_DeleteInterp(interp);
+ g_free(buf);
+ return status;
+}
+
+static gboolean tcl_load_plugin(GaimPlugin *plugin)
+{
+ struct tcl_plugin_data *data;
+ Tcl_Interp *interp;
+ Tcl_Obj *result;
+
+ plugin->extra = NULL;
+
+ if ((interp = tcl_create_interp()) == NULL) {
+ gaim_debug(GAIM_DEBUG_ERROR, "tcl", "Could not initialize Tcl interpreter\n");
+ return FALSE;
+ }
+
+ Tcl_SourceRCFile(interp);
+
+ if (Tcl_EvalFile(interp, plugin->path) != TCL_OK) {
+ result = Tcl_GetObjResult(interp);
+ gaim_debug(GAIM_DEBUG_ERROR, "tcl",
+ "Error evaluating %s: %s\n", plugin->path,
+ Tcl_GetString(result));
+ Tcl_DeleteInterp(interp);
+ return FALSE;
+ }
+
+ Tcl_Preserve((ClientData)interp);
+
+ data = g_new0(struct tcl_plugin_data, 1);
+ data->plugin = plugin;
+ data->interp = interp;
+ plugin->extra = data;
+
+ g_hash_table_insert(tcl_plugins, (gpointer)interp, (gpointer)data);
+
+ return TRUE;
+}
+
+static gboolean tcl_unload_plugin(GaimPlugin *plugin)
+{
+ struct tcl_plugin_data *data;
+
+ if (plugin == NULL)
+ return TRUE;
+
+ data = plugin->extra;
+
+ if (data != NULL) {
+ g_hash_table_remove(tcl_plugins, (gpointer)(data->interp));
+ gaim_signals_disconnect_by_handle(data->interp);
+ tcl_cmd_cleanup(data->interp);
+ tcl_signal_cleanup(data->interp);
+ Tcl_Release((ClientData)data->interp);
+ Tcl_DeleteInterp(data->interp);
+ g_free(data);
+ }
+
+ return TRUE;
+}
+
+static void tcl_destroy_plugin(GaimPlugin *plugin)
+{
+ if (plugin->info != NULL) {
+ g_free(plugin->info->id);
+ g_free(plugin->info->name);
+ g_free(plugin->info->version);
+ g_free(plugin->info->description);
+ g_free(plugin->info->author);
+ g_free(plugin->info->homepage);
+ }
+
+ return;
+}
+
+static gboolean tcl_load(GaimPlugin *plugin)
+{
+ if(!tcl_loaded)
+ return FALSE;
+ tcl_glib_init();
+ tcl_cmd_init();
+ tcl_signal_init();
+ gaim_tcl_ref_init();
+
+ GaimTclRefAccount = gaim_stringref_new("Account");
+ GaimTclRefConnection = gaim_stringref_new("Connection");
+ GaimTclRefConversation = gaim_stringref_new("Conversation");
+ GaimTclRefPointer = gaim_stringref_new("Pointer");
+ GaimTclRefPlugin = gaim_stringref_new("Plugin");
+ GaimTclRefPresence = gaim_stringref_new("Presence");
+ GaimTclRefStatus = gaim_stringref_new("Status");
+ GaimTclRefStatusAttr = gaim_stringref_new("StatusAttr");
+ GaimTclRefStatusType = gaim_stringref_new("StatusType");
+ GaimTclRefXfer = gaim_stringref_new("Xfer");
+
+ tcl_plugins = g_hash_table_new(g_direct_hash, g_direct_equal);
+
+#ifdef HAVE_TK
+ Tcl_StaticPackage(NULL, "Tk", Tk_Init, Tk_SafeInit);
+#endif /* HAVE_TK */
+
+ return TRUE;
+}
+
+static gboolean tcl_unload(GaimPlugin *plugin)
+{
+ g_hash_table_destroy(tcl_plugins);
+ tcl_plugins = NULL;
+
+ gaim_stringref_unref(GaimTclRefAccount);
+ gaim_stringref_unref(GaimTclRefConnection);
+ gaim_stringref_unref(GaimTclRefConversation);
+ gaim_stringref_unref(GaimTclRefPointer);
+ gaim_stringref_unref(GaimTclRefPlugin);
+ gaim_stringref_unref(GaimTclRefPresence);
+ gaim_stringref_unref(GaimTclRefStatus);
+ gaim_stringref_unref(GaimTclRefStatusAttr);
+ gaim_stringref_unref(GaimTclRefStatusType);
+ gaim_stringref_unref(GaimTclRefXfer);
+
+ return TRUE;
+}
+
+static GaimPluginLoaderInfo tcl_loader_info =
+{
+ NULL,
+ tcl_probe_plugin,
+ tcl_load_plugin,
+ tcl_unload_plugin,
+ tcl_destroy_plugin,
+};
+
+static GaimPluginInfo tcl_info =
+{
+ GAIM_PLUGIN_MAGIC,
+ GAIM_MAJOR_VERSION,
+ GAIM_MINOR_VERSION,
+ GAIM_PLUGIN_LOADER,
+ NULL,
+ 0,
+ NULL,
+ GAIM_PRIORITY_DEFAULT,
+ "core-tcl",
+ N_("Tcl Plugin Loader"),
+ VERSION,
+ N_("Provides support for loading Tcl plugins"),
+ N_("Provides support for loading Tcl plugins"),
+ "Ethan Blanton <eblanton@cs.purdue.edu>",
+ GAIM_WEBSITE,
+ tcl_load,
+ tcl_unload,
+ NULL,
+ NULL,
+ &tcl_loader_info,
+ NULL,
+ NULL
+};
+
+#ifdef _WIN32
+typedef Tcl_Interp* (CALLBACK* LPFNTCLCREATEINTERP)(void);
+typedef void (CALLBACK* LPFNTKINIT)(Tcl_Interp*);
+
+LPFNTCLCREATEINTERP wtcl_CreateInterp = NULL;
+LPFNTKINIT wtk_Init = NULL;
+#undef Tcl_CreateInterp
+#define Tcl_CreateInterp wtcl_CreateInterp
+#undef Tk_Init
+#define Tk_Init wtk_Init
+
+static gboolean tcl_win32_init() {
+ const char regkey[] = "SOFTWARE\\ActiveState\\ActiveTcl\\";
+ char *version = NULL;
+ gboolean retval = FALSE;
+
+ if ((version = wgaim_read_reg_string(HKEY_LOCAL_MACHINE, regkey, "CurrentVersion"))
+ || (version = wgaim_read_reg_string(HKEY_CURRENT_USER, regkey, "CurrentVersion"))) {
+ char *path;
+ char *regkey2;
+
+ regkey2 = g_strdup_printf("%s%s\\", regkey, version);
+ if ((path = wgaim_read_reg_string(HKEY_LOCAL_MACHINE, regkey2, NULL)) || (path = wgaim_read_reg_string(HKEY_CURRENT_USER, regkey2, NULL))) {
+ char *tclpath;
+ char *tkpath;
+
+ gaim_debug(GAIM_DEBUG_INFO, "tcl", "Loading ActiveTCL version %s from \"%s\"\n", version, path);
+
+ tclpath = g_build_filename(path, "bin", "tcl84.dll", NULL);
+ tkpath = g_build_filename(path, "bin", "tk84.dll", NULL);
+
+ if(!(wtcl_CreateInterp = (LPFNTCLCREATEINTERP) wgaim_find_and_loadproc(tclpath, "Tcl_CreateInterp"))) {
+ gaim_debug(GAIM_DEBUG_INFO, "tcl", "tcl_win32_init error loading Tcl_CreateInterp\n");
+ } else {
+ if(!(wtk_Init = (LPFNTKINIT) wgaim_find_and_loadproc(tkpath, "Tk_Init"))) {
+ HMODULE mod;
+ gaim_debug(GAIM_DEBUG_INFO, "tcl", "tcl_win32_init error loading Tk_Init\n");
+ if((mod = GetModuleHandle("tcl84.dll")))
+ FreeLibrary(mod);
+ } else {
+ retval = TRUE;
+ }
+ }
+ g_free(tclpath);
+ g_free(tkpath);
+ }
+ g_free(path);
+ g_free(regkey2);
+ }
+
+ g_free(version);
+
+ if (!retval)
+ gaim_debug(GAIM_DEBUG_INFO, "tcl", _("Unable to detect ActiveTCL installation. If you wish to use TCL plugins, install ActiveTCL from http://www.activestate.com\n"));
+
+ return retval;
+}
+
+#endif /* _WIN32 */
+
+static void tcl_init_plugin(GaimPlugin *plugin)
+{
+#ifdef USE_TCL_STUBS
+ Tcl_Interp *interp = NULL;
+#endif
+ _tcl_plugin = plugin;
+
+#ifdef USE_TCL_STUBS
+#ifdef _WIN32
+ if(!tcl_win32_init())
+ return;
+#endif
+ if(!(interp = Tcl_CreateInterp()))
+ return;
+
+ if(!Tcl_InitStubs(interp, TCL_VERSION, 0)) {
+ gaim_debug(GAIM_DEBUG_ERROR, "tcl", "Tcl_InitStubs: %s\n", interp->result);
+ return;
+ }
+#endif
+
+ Tcl_FindExecutable("gaim");
+
+#if defined(USE_TK_STUBS) && defined(HAVE_TK)
+ Tk_Init(interp);
+
+ if(!Tk_InitStubs(interp, TK_VERSION, 0)) {
+ gaim_debug(GAIM_DEBUG_ERROR, "tcl", "Error Tk_InitStubs: %s\n", interp->result);
+ Tcl_DeleteInterp(interp);
+ return;
+ }
+#endif
+ tcl_loaded = TRUE;
+#ifdef USE_TCL_STUBS
+ Tcl_DeleteInterp(interp);
+#endif
+ tcl_loader_info.exts = g_list_append(tcl_loader_info.exts, "tcl");
+}
+
+GAIM_INIT_PLUGIN(tcl, tcl_init_plugin, tcl_info)
diff --git a/libpurple/plugins/tcl/tcl_cmd.c b/libpurple/plugins/tcl/tcl_cmd.c
new file mode 100644
index 0000000000..c5f9605e01
--- /dev/null
+++ b/libpurple/plugins/tcl/tcl_cmd.c
@@ -0,0 +1,190 @@
+/**
+ * @file tcl_cmd.c Gaim Tcl cmd API
+ *
+ * gaim
+ *
+ * Copyright (C) 2006 Etan Reisner <deryni@gmail.com>
+ *
+ * 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 <tcl.h>
+
+#include "tcl_gaim.h"
+
+#include "internal.h"
+#include "cmds.h"
+#include "debug.h"
+
+static GList *tcl_cmd_callbacks;
+
+static GaimCmdRet tcl_cmd_callback(GaimConversation *conv, const gchar *cmd,
+ gchar **args, gchar **errors,
+ struct tcl_cmd_handler *handler);
+static Tcl_Obj *new_cmd_cb_namespace(void);
+
+void tcl_cmd_init()
+{
+ tcl_cmd_callbacks = NULL;
+}
+
+void tcl_cmd_handler_free(struct tcl_cmd_handler *handler)
+{
+ if (handler == NULL)
+ return;
+
+ Tcl_DecrRefCount(handler->namespace);
+ g_free(handler);
+}
+
+void tcl_cmd_cleanup(Tcl_Interp *interp)
+{
+ GList *cur;
+ struct tcl_cmd_handler *handler;
+
+ for (cur = tcl_cmd_callbacks; cur != NULL; cur = g_list_next(cur)) {
+ handler = cur->data;
+ if (handler->interp == interp) {
+ gaim_cmd_unregister(handler->id);
+ tcl_cmd_handler_free(handler);
+ cur->data = NULL;
+ }
+ }
+ tcl_cmd_callbacks = g_list_remove_all(tcl_cmd_callbacks, NULL);
+}
+
+GaimCmdId tcl_cmd_register(struct tcl_cmd_handler *handler)
+{
+ int id;
+ GString *proc;
+
+ if ((id = gaim_cmd_register(Tcl_GetString(handler->cmd),
+ handler->args, handler->priority,
+ handler->flags, handler->prpl_id,
+ GAIM_CMD_FUNC(tcl_cmd_callback),
+ handler->helpstr, (void *)handler)) == 0)
+ return 0;
+
+ handler->namespace = new_cmd_cb_namespace ();
+ Tcl_IncrRefCount(handler->namespace);
+ proc = g_string_new("");
+ g_string_append_printf(proc, "namespace eval %s { proc cb { conv cmd arglist } { %s } }",
+ Tcl_GetString(handler->namespace),
+ Tcl_GetString(handler->proc));
+ if (Tcl_Eval(handler->interp, proc->str) != TCL_OK) {
+ Tcl_DecrRefCount(handler->namespace);
+ g_string_free(proc, TRUE);
+ return 0;
+ }
+ g_string_free(proc, TRUE);
+
+ tcl_cmd_callbacks = g_list_append(tcl_cmd_callbacks, (gpointer)handler);
+
+ return id;
+}
+
+void tcl_cmd_unregister(GaimCmdId id, Tcl_Interp *interp)
+{
+ GList *cur;
+ GString *cmd;
+ gboolean found = FALSE;
+ struct tcl_cmd_handler *handler;
+
+ for (cur = tcl_cmd_callbacks; cur != NULL; cur = g_list_next(cur)) {
+ handler = cur->data;
+ if (handler->interp == interp && handler->id == id) {
+ gaim_cmd_unregister(id);
+ cmd = g_string_sized_new(64);
+ g_string_printf(cmd, "namespace delete %s",
+ Tcl_GetString(handler->namespace));
+ Tcl_EvalEx(interp, cmd->str, -1, TCL_EVAL_GLOBAL);
+ tcl_cmd_handler_free(handler);
+ g_string_free(cmd, TRUE);
+ cur->data = NULL;
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (found)
+ tcl_cmd_callbacks = g_list_remove_all(tcl_cmd_callbacks, NULL);
+}
+
+static GaimCmdRet tcl_cmd_callback(GaimConversation *conv, const gchar *cmd,
+ gchar **args, gchar **errors,
+ struct tcl_cmd_handler *handler)
+{
+ int retval, error, i;
+ Tcl_Obj *command, *arg, *tclargs, *result;
+
+ command = Tcl_NewListObj(0, NULL);
+ Tcl_IncrRefCount(command);
+
+ /* The callback */
+ arg = Tcl_DuplicateObj(handler->namespace);
+ Tcl_AppendStringsToObj(arg, "::cb", NULL);
+ Tcl_ListObjAppendElement(handler->interp, command, arg);
+
+ /* The conversation */
+ arg = gaim_tcl_ref_new(GaimTclRefConversation, conv);
+ Tcl_ListObjAppendElement(handler->interp, command, arg);
+
+ /* The command */
+ arg = Tcl_NewStringObj(cmd, -1);
+ Tcl_ListObjAppendElement(handler->interp, command, arg);
+
+ /* The args list */
+ tclargs = Tcl_NewListObj(0, NULL);
+ for (i = 0; i < handler->nargs; i++) {
+ arg = Tcl_NewStringObj(args[i], -1);
+
+ Tcl_ListObjAppendElement(handler->interp, tclargs, arg);
+ }
+ Tcl_ListObjAppendElement(handler->interp, command, tclargs);
+
+ if ((error = Tcl_EvalObjEx(handler->interp, command,
+ TCL_EVAL_GLOBAL)) != TCL_OK) {
+ gchar *errorstr;
+
+ errorstr = g_strdup_printf("error evaluating callback: %s\n",
+ Tcl_GetString(Tcl_GetObjResult(handler->interp)));
+ gaim_debug(GAIM_DEBUG_ERROR, "tcl", errorstr);
+ *errors = errorstr;
+ retval = GAIM_CMD_RET_FAILED;
+ } else {
+ result = Tcl_GetObjResult(handler->interp);
+ if ((error = Tcl_GetIntFromObj(handler->interp, result,
+ &retval)) != TCL_OK) {
+ gchar *errorstr;
+
+ errorstr = g_strdup_printf("Error retreiving procedure result: %s\n",
+ Tcl_GetString(Tcl_GetObjResult(handler->interp)));
+ gaim_debug(GAIM_DEBUG_ERROR, "tcl", errorstr);
+ *errors = errorstr;
+ retval = GAIM_CMD_RET_FAILED;
+ }
+ }
+
+ return retval;
+}
+
+static Tcl_Obj *new_cmd_cb_namespace()
+{
+ char name[32];
+ static int cbnum;
+
+ g_snprintf(name, sizeof(name), "::gaim::_cmd_callback::cb_%d",
+ cbnum++);
+ return Tcl_NewStringObj(name, -1);
+}
diff --git a/libpurple/plugins/tcl/tcl_cmds.c b/libpurple/plugins/tcl/tcl_cmds.c
new file mode 100644
index 0000000000..5763eeb14d
--- /dev/null
+++ b/libpurple/plugins/tcl/tcl_cmds.c
@@ -0,0 +1,1607 @@
+/**
+ * @file tcl_cmds.c Commands for the Gaim Tcl plugin bindings
+ *
+ * gaim
+ *
+ * Copyright (C) 2003 Ethan Blanton <eblanton@cs.purdue.edu>
+ *
+ * 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 <tcl.h>
+
+#include "internal.h"
+#include "conversation.h"
+#include "connection.h"
+#include "account.h"
+#include "server.h"
+#include "notify.h"
+#include "blist.h"
+#include "debug.h"
+#include "prefs.h"
+#include "core.h"
+
+#include "tcl_gaim.h"
+
+static GaimAccount *tcl_validate_account(Tcl_Obj *obj, Tcl_Interp *interp);
+static GaimConversation *tcl_validate_conversation(Tcl_Obj *obj, Tcl_Interp *interp);
+static GaimConnection *tcl_validate_gc(Tcl_Obj *obj, Tcl_Interp *interp);
+
+static GaimAccount *tcl_validate_account(Tcl_Obj *obj, Tcl_Interp *interp)
+{
+ GaimAccount *account;
+ GList *cur;
+
+ account = gaim_tcl_ref_get(interp, obj, GaimTclRefAccount);
+
+ if (account == NULL)
+ return NULL;
+
+ for (cur = gaim_accounts_get_all(); cur != NULL; cur = g_list_next(cur)) {
+ if (account == cur->data)
+ return account;
+ }
+ if (interp != NULL)
+ Tcl_SetStringObj(Tcl_GetObjResult(interp), "invalid account", -1);
+ return NULL;
+}
+
+static GaimConversation *tcl_validate_conversation(Tcl_Obj *obj, Tcl_Interp *interp)
+{
+ GaimConversation *convo;
+ GList *cur;
+
+ convo = gaim_tcl_ref_get(interp, obj, GaimTclRefConversation);
+
+ if (convo == NULL)
+ return NULL;
+
+ for (cur = gaim_get_conversations(); cur != NULL; cur = g_list_next(cur)) {
+ if (convo == cur->data)
+ return convo;
+ }
+ if (interp != NULL)
+ Tcl_SetStringObj(Tcl_GetObjResult(interp), "invalid conversation", -1);
+ return NULL;
+}
+
+static GaimConnection *tcl_validate_gc(Tcl_Obj *obj, Tcl_Interp *interp)
+{
+ GaimConnection *gc;
+ GList *cur;
+
+ gc = gaim_tcl_ref_get(interp, obj, GaimTclRefConnection);
+
+ if (gc == NULL)
+ return NULL;
+
+ for (cur = gaim_connections_get_all(); cur != NULL; cur = g_list_next(cur)) {
+ if (gc == cur->data)
+ return gc;
+ }
+ return NULL;
+}
+
+int tcl_cmd_account(ClientData unused, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
+{
+ Tcl_Obj *result = Tcl_GetObjResult(interp), *list, *elem;
+ const char *cmds[] = { "alias", "connect", "connection", "disconnect",
+ "enabled", "find", "handle", "isconnected",
+ "list", "presence", "protocol", "status",
+ "status_type", "status_types", "username",
+ NULL };
+ enum { CMD_ACCOUNT_ALIAS,
+ CMD_ACCOUNT_CONNECT, CMD_ACCOUNT_CONNECTION,
+ CMD_ACCOUNT_DISCONNECT, CMD_ACCOUNT_ENABLED, CMD_ACCOUNT_FIND,
+ CMD_ACCOUNT_HANDLE, CMD_ACCOUNT_ISCONNECTED, CMD_ACCOUNT_LIST,
+ CMD_ACCOUNT_PRESENCE, CMD_ACCOUNT_PROTOCOL, CMD_ACCOUNT_STATUS,
+ CMD_ACCOUNT_STATUS_TYPE, CMD_ACCOUNT_STATUS_TYPES,
+ CMD_ACCOUNT_USERNAME } cmd;
+ const char *listopts[] = { "-all", "-online", NULL };
+ enum { CMD_ACCOUNTLIST_ALL, CMD_ACCOUNTLIST_ONLINE } listopt;
+ const char *alias;
+ const GList *cur;
+ GaimAccount *account;
+ GaimStatus *status;
+ GaimStatusType *status_type;
+ GaimValue *value;
+ char *attr_id;
+ int error;
+ int b, i;
+
+ if (objc < 2) {
+ Tcl_WrongNumArgs(interp, 1, objv, "subcommand ?args?");
+ return TCL_ERROR;
+ }
+
+ if ((error = Tcl_GetIndexFromObj(interp, objv[1], cmds, "subcommand", 0, (int *)&cmd)) != TCL_OK)
+ return error;
+
+ switch (cmd) {
+ case CMD_ACCOUNT_ALIAS:
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "account");
+ return TCL_ERROR;
+ }
+ if ((account = tcl_validate_account(objv[2], interp)) == NULL)
+ return TCL_ERROR;
+ alias = gaim_account_get_alias(account);
+ Tcl_SetStringObj(result, alias ? (char *)alias : "", -1);
+ break;
+ case CMD_ACCOUNT_CONNECT:
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "account");
+ return TCL_ERROR;
+ }
+ if ((account = tcl_validate_account(objv[2], interp)) == NULL)
+ return TCL_ERROR;
+ if (!gaim_account_is_connected(account))
+ gaim_account_connect(account);
+ Tcl_SetObjResult(interp,
+ gaim_tcl_ref_new(GaimTclRefConnection,
+ gaim_account_get_connection(account)));
+ break;
+ case CMD_ACCOUNT_CONNECTION:
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "account");
+ return TCL_ERROR;
+ }
+
+ if ((account = tcl_validate_account(objv[2], interp)) == NULL)
+ return TCL_ERROR;
+ Tcl_SetObjResult(interp,
+ gaim_tcl_ref_new(GaimTclRefConnection,
+ gaim_account_get_connection(account)));
+ break;
+ case CMD_ACCOUNT_DISCONNECT:
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "account");
+ return TCL_ERROR;
+ }
+ if ((account = tcl_validate_account(objv[2], interp)) == NULL)
+ return TCL_ERROR;
+ gaim_account_disconnect(account);
+ break;
+ case CMD_ACCOUNT_ENABLED:
+ if (objc != 3 && objc != 4) {
+ Tcl_WrongNumArgs(interp, 2, objv, "account ?enabled?");
+ return TCL_ERROR;
+ }
+ if ((account = tcl_validate_account(objv[2], interp)) == NULL)
+ return TCL_ERROR;
+ if (objc == 3) {
+ Tcl_SetBooleanObj(result,
+ gaim_account_get_enabled(account,
+ gaim_core_get_ui()));
+ } else {
+ if ((error = Tcl_GetBooleanFromObj(interp, objv[3], &b)) != TCL_OK)
+ return TCL_ERROR;
+ gaim_account_set_enabled(account, gaim_core_get_ui(), b);
+ }
+ break;
+ case CMD_ACCOUNT_FIND:
+ if (objc != 4) {
+ Tcl_WrongNumArgs(interp, 2, objv, "username protocol");
+ return TCL_ERROR;
+ }
+ account = gaim_accounts_find(Tcl_GetString(objv[2]),
+ Tcl_GetString(objv[3]));
+ Tcl_SetObjResult(interp,
+ gaim_tcl_ref_new(GaimTclRefAccount, account));
+ break;
+ case CMD_ACCOUNT_HANDLE:
+ if (objc != 2) {
+ Tcl_WrongNumArgs(interp, 2, objv, "");
+ return TCL_ERROR;
+ }
+ Tcl_SetIntObj(result, (int)gaim_accounts_get_handle());
+ break;
+ case CMD_ACCOUNT_ISCONNECTED:
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "account");
+ return TCL_ERROR;
+ }
+ if ((account = tcl_validate_account(objv[2], interp)) == NULL)
+ return TCL_ERROR;
+ Tcl_SetBooleanObj(result, gaim_account_is_connected(account));
+ break;
+ case CMD_ACCOUNT_LIST:
+ listopt = CMD_ACCOUNTLIST_ALL;
+ if (objc > 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "?option?");
+ return TCL_ERROR;
+ }
+ if (objc == 3) {
+ if ((error = Tcl_GetIndexFromObj(interp, objv[2], listopts, "option", 0, (int *)&listopt)) != TCL_OK)
+ return error;
+ }
+ list = Tcl_NewListObj(0, NULL);
+ for (cur = gaim_accounts_get_all(); cur != NULL; cur = g_list_next(cur)) {
+ account = cur->data;
+ if (listopt == CMD_ACCOUNTLIST_ONLINE && !gaim_account_is_connected(account))
+ continue;
+ elem = gaim_tcl_ref_new(GaimTclRefAccount, account);
+ Tcl_ListObjAppendElement(interp, list, elem);
+ }
+ Tcl_SetObjResult(interp, list);
+ break;
+ case CMD_ACCOUNT_PRESENCE:
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "account");
+ return TCL_ERROR;
+ }
+ if ((account = tcl_validate_account(objv[2], interp)) == NULL)
+ return TCL_ERROR;
+ Tcl_SetObjResult(interp, gaim_tcl_ref_new(GaimTclRefPresence,
+ gaim_account_get_presence(account)));
+ break;
+ case CMD_ACCOUNT_PROTOCOL:
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "account");
+ return TCL_ERROR;
+ }
+ if ((account = tcl_validate_account(objv[2], interp)) == NULL)
+ return TCL_ERROR;
+ Tcl_SetStringObj(result, (char *)gaim_account_get_protocol_id(account), -1);
+ break;
+ case CMD_ACCOUNT_STATUS:
+ if (objc < 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "account ?status_id name value ...?");
+ return TCL_ERROR;
+ }
+ if ((account = tcl_validate_account(objv[2], interp)) == NULL)
+ return TCL_ERROR;
+ if (objc == 3) {
+ Tcl_SetObjResult(interp,
+ gaim_tcl_ref_new(GaimTclRefStatus,
+ gaim_account_get_active_status(account)));
+ } else {
+ GList *l = NULL;
+ if (objc % 2) {
+ Tcl_SetStringObj(result, "name without value setting status", -1);
+ return TCL_ERROR;
+ }
+ status = gaim_account_get_status(account, Tcl_GetString(objv[3]));
+ if (status == NULL) {
+ Tcl_SetStringObj(result, "invalid status for account", -1);
+ return TCL_ERROR;
+ }
+ for (i = 4; i < objc; i += 2) {
+ attr_id = Tcl_GetString(objv[i]);
+ value = gaim_status_get_attr_value(status, attr_id);
+ if (value == NULL) {
+ Tcl_SetStringObj(result, "invalid attribute for account", -1);
+ return TCL_ERROR;
+ }
+ switch (gaim_value_get_type(value)) {
+ case GAIM_TYPE_BOOLEAN:
+ error = Tcl_GetBooleanFromObj(interp, objv[i + 1], &b);
+ if (error != TCL_OK)
+ return error;
+ l = g_list_append(l, attr_id);
+ l = g_list_append(l, GINT_TO_POINTER(b));
+ break;
+ case GAIM_TYPE_INT:
+ error = Tcl_GetIntFromObj(interp, objv[i + 1], &b);
+ if (error != TCL_OK)
+ return error;
+ l = g_list_append(l, attr_id);
+ l = g_list_append(l, GINT_TO_POINTER(b));
+ break;
+ case GAIM_TYPE_STRING:
+ l = g_list_append(l, attr_id);
+ l = g_list_append(l, Tcl_GetString(objv[i + 1]));
+ break;
+ default:
+ Tcl_SetStringObj(result, "unknown GaimValue type", -1);
+ return TCL_ERROR;
+ }
+ }
+ gaim_account_set_status_list(account, Tcl_GetString(objv[3]), TRUE, l);
+ g_list_free(l);
+ }
+ break;
+ case CMD_ACCOUNT_STATUS_TYPE:
+ if (objc != 4 && objc != 5) {
+ Tcl_WrongNumArgs(interp, 2, objv, "account ?statustype? ?-primitive primitive?");
+ return TCL_ERROR;
+ }
+ if ((account = tcl_validate_account(objv[2], interp)) == NULL)
+ return TCL_ERROR;
+ if (objc == 4) {
+ status_type = gaim_account_get_status_type(account,
+ Tcl_GetString(objv[3]));
+ } else {
+ GaimStatusPrimitive primitive;
+ if (strcmp(Tcl_GetString(objv[3]), "-primitive")) {
+ Tcl_SetStringObj(result, "bad option \"", -1);
+ Tcl_AppendObjToObj(result, objv[3]);
+ Tcl_AppendToObj(result,
+ "\": should be -primitive", -1);
+ return TCL_ERROR;
+ }
+ primitive = gaim_primitive_get_type_from_id(Tcl_GetString(objv[4]));
+ status_type = gaim_account_get_status_type_with_primitive(account,
+ primitive);
+ }
+ if (status_type == NULL) {
+ Tcl_SetStringObj(result, "status type not found", -1);
+ return TCL_ERROR;
+ }
+ Tcl_SetObjResult(interp,
+ gaim_tcl_ref_new(GaimTclRefStatusType,
+ status_type));
+ break;
+ case CMD_ACCOUNT_STATUS_TYPES:
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "account");
+ return TCL_ERROR;
+ }
+ if ((account = tcl_validate_account(objv[2], interp)) == NULL)
+ return TCL_ERROR;
+ list = Tcl_NewListObj(0, NULL);
+ for (cur = gaim_account_get_status_types(account); cur != NULL;
+ cur = g_list_next(cur)) {
+ Tcl_ListObjAppendElement(interp, list,
+ gaim_tcl_ref_new(GaimTclRefStatusType,
+ cur->data));
+ }
+ Tcl_SetObjResult(interp, list);
+ break;
+ case CMD_ACCOUNT_USERNAME:
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "account");
+ return TCL_ERROR;
+ }
+ if ((account = tcl_validate_account(objv[2], interp)) == NULL)
+ return TCL_ERROR;
+ Tcl_SetStringObj(result, (char *)gaim_account_get_username(account), -1);
+ break;
+ }
+
+ return TCL_OK;
+}
+
+static GaimBlistNode *tcl_list_to_buddy(Tcl_Interp *interp, int count, Tcl_Obj **elems)
+{
+ GaimBlistNode *node = NULL;
+ GaimAccount *account;
+ char *name;
+ char *type;
+
+ if (count < 3) {
+ Tcl_SetStringObj(Tcl_GetObjResult(interp), "list too short", -1);
+ return NULL;
+ }
+
+ type = Tcl_GetString(elems[0]);
+ name = Tcl_GetString(elems[1]);
+ if ((account = tcl_validate_account(elems[2], interp)) == NULL)
+ return NULL;
+
+ if (!strcmp(type, "buddy")) {
+ node = (GaimBlistNode *)gaim_find_buddy(account, name);
+ } else if (!strcmp(type, "group")) {
+ node = (GaimBlistNode *)gaim_blist_find_chat(account, name);
+ }
+
+ return node;
+}
+
+int tcl_cmd_buddy(ClientData unused, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
+{
+ Tcl_Obj *list, *tclgroup, *tclgrouplist, *tclcontact, *tclcontactlist, *tclbud, **elems, *result;
+ const char *cmds[] = { "alias", "handle", "info", "list", NULL };
+ enum { CMD_BUDDY_ALIAS, CMD_BUDDY_HANDLE, CMD_BUDDY_INFO, CMD_BUDDY_LIST } cmd;
+ GaimBuddyList *blist;
+ GaimBlistNode *node, *gnode, *bnode;
+ GaimAccount *account;
+ GaimBuddy *bud;
+ GaimChat *cnode;
+ int error, all = 0, count;
+
+ if (objc < 2) {
+ Tcl_WrongNumArgs(interp, 1, objv, "subcommand ?args?");
+ return TCL_ERROR;
+ }
+ if ((error = Tcl_GetIndexFromObj(interp, objv[1], cmds, "subcommand", 0, (int *)&cmd)) != TCL_OK)
+ return error;
+
+ result = Tcl_GetObjResult(interp);
+
+ switch (cmd) {
+ case CMD_BUDDY_ALIAS:
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "buddy");
+ return TCL_ERROR;
+ }
+ if ((error = Tcl_ListObjGetElements(interp, objv[2], &count, &elems)) != TCL_OK)
+ return error;
+ if ((node = tcl_list_to_buddy(interp, count, elems)) == NULL)
+ return TCL_ERROR;
+ if (node->type == GAIM_BLIST_CHAT_NODE)
+ Tcl_SetStringObj(result, ((GaimChat *)node)->alias, -1);
+ else if (node->type == GAIM_BLIST_BUDDY_NODE)
+ Tcl_SetStringObj(result, (char *)gaim_buddy_get_alias((GaimBuddy *)node), -1);
+ return TCL_OK;
+ break;
+ case CMD_BUDDY_HANDLE:
+ if (objc != 2) {
+ Tcl_WrongNumArgs(interp, 2, objv, "");
+ return TCL_ERROR;
+ }
+ Tcl_SetIntObj(result, (int)gaim_blist_get_handle());
+ break;
+ case CMD_BUDDY_INFO:
+ if (objc != 3 && objc != 4) {
+ Tcl_WrongNumArgs(interp, 2, objv, "( buddy | account username )");
+ return TCL_ERROR;
+ }
+ if (objc == 3) {
+ if ((error = Tcl_ListObjGetElements(interp, objv[2], &count, &elems)) != TCL_OK)
+ return error;
+ if (count < 3) {
+ Tcl_SetStringObj(result, "buddy too short", -1);
+ return TCL_ERROR;
+ }
+ if (strcmp("buddy", Tcl_GetString(elems[0]))) {
+ Tcl_SetStringObj(result, "invalid buddy", -1);
+ return TCL_ERROR;
+ }
+ if ((account = tcl_validate_account(elems[2], interp)) == NULL)
+ return TCL_ERROR;
+ serv_get_info(gaim_account_get_connection(account), Tcl_GetString(elems[1]));
+ } else {
+ if ((account = tcl_validate_account(objv[2], interp)) == NULL)
+ return TCL_ERROR;
+ serv_get_info(gaim_account_get_connection(account), Tcl_GetString(objv[3]));
+ }
+ break;
+ case CMD_BUDDY_LIST:
+ if (objc == 3) {
+ if (!strcmp("-all", Tcl_GetString(objv[2]))) {
+ all = 1;
+ } else {
+ Tcl_SetStringObj(result, "", -1);
+ Tcl_AppendStringsToObj(result, "unknown option: ", Tcl_GetString(objv[2]), NULL);
+ return TCL_ERROR;
+ }
+ }
+ list = Tcl_NewListObj(0, NULL);
+ blist = gaim_get_blist();
+ for (gnode = blist->root; gnode != NULL; gnode = gnode->next) {
+ tclgroup = Tcl_NewListObj(0, NULL);
+ Tcl_ListObjAppendElement(interp, tclgroup, Tcl_NewStringObj("group", -1));
+ Tcl_ListObjAppendElement(interp, tclgroup,
+ Tcl_NewStringObj(((GaimGroup *)gnode)->name, -1));
+ tclgrouplist = Tcl_NewListObj(0, NULL);
+ for (node = gnode->child; node != NULL; node = node->next) {
+ switch (node->type) {
+ case GAIM_BLIST_CONTACT_NODE:
+ tclcontact = Tcl_NewListObj(0, NULL);
+ Tcl_IncrRefCount(tclcontact);
+ Tcl_ListObjAppendElement(interp, tclcontact, Tcl_NewStringObj("contact", -1));
+ tclcontactlist = Tcl_NewListObj(0, NULL);
+ Tcl_IncrRefCount(tclcontactlist);
+ count = 0;
+ for (bnode = node->child; bnode != NULL; bnode = bnode ->next) {
+ if (bnode->type != GAIM_BLIST_BUDDY_NODE)
+ continue;
+ bud = (GaimBuddy *)bnode;
+ if (!all && !gaim_account_is_connected(bud->account))
+ continue;
+ count++;
+ tclbud = Tcl_NewListObj(0, NULL);
+ Tcl_ListObjAppendElement(interp, tclbud, Tcl_NewStringObj("buddy", -1));
+ Tcl_ListObjAppendElement(interp, tclbud, Tcl_NewStringObj(bud->name, -1));
+ Tcl_ListObjAppendElement(interp, tclbud, gaim_tcl_ref_new(GaimTclRefAccount, bud->account));
+ Tcl_ListObjAppendElement(interp, tclcontactlist, tclbud);
+ }
+ if (count) {
+ Tcl_ListObjAppendElement(interp, tclcontact, tclcontactlist);
+ Tcl_ListObjAppendElement(interp, tclgrouplist, tclcontact);
+ }
+ Tcl_DecrRefCount(tclcontact);
+ Tcl_DecrRefCount(tclcontactlist);
+ break;
+ case GAIM_BLIST_CHAT_NODE:
+ cnode = (GaimChat *)node;
+ if (!all && !gaim_account_is_connected(cnode->account))
+ continue;
+ tclbud = Tcl_NewListObj(0, NULL);
+ Tcl_ListObjAppendElement(interp, tclbud, Tcl_NewStringObj("chat", -1));
+ Tcl_ListObjAppendElement(interp, tclbud, Tcl_NewStringObj(cnode->alias, -1));
+ Tcl_ListObjAppendElement(interp, tclbud, gaim_tcl_ref_new(GaimTclRefAccount, cnode->account));
+ Tcl_ListObjAppendElement(interp, tclgrouplist, tclbud);
+ break;
+ default:
+ gaim_debug(GAIM_DEBUG_WARNING, "tcl", "Unexpected buddy type %d", node->type);
+ continue;
+ }
+ }
+ Tcl_ListObjAppendElement(interp, tclgroup, tclgrouplist);
+ Tcl_ListObjAppendElement(interp, list, tclgroup);
+ }
+ Tcl_SetObjResult(interp, list);
+ break;
+ }
+
+ return TCL_OK;
+}
+
+int tcl_cmd_cmd(ClientData unused, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
+{
+ const char *cmds[] = { "register", "unregister", NULL };
+ enum { CMD_CMD_REGISTER, CMD_CMD_UNREGISTER } cmd;
+ struct tcl_cmd_handler *handler;
+ Tcl_Obj *result = Tcl_GetObjResult(interp);
+ GaimCmdId id;
+ int error;
+
+ if (objc < 2) {
+ Tcl_WrongNumArgs(interp, 1, objv, "subcommand ?args?");
+ return TCL_ERROR;
+ }
+
+ if ((error = Tcl_GetIndexFromObj(interp, objv[1], cmds, "subcommand", 0, (int *)&cmd)) != TCL_OK)
+ return error;
+
+ switch (cmd) {
+ case CMD_CMD_REGISTER:
+ if (objc != 9) {
+ Tcl_WrongNumArgs(interp, 2, objv, "cmd arglist priority flags prpl_id proc helpstr");
+ return TCL_ERROR;
+ }
+ handler = g_new0(struct tcl_cmd_handler, 1);
+ handler->cmd = objv[2];
+ handler->args = Tcl_GetString(objv[3]);
+ handler->nargs = strlen(handler->args);
+ if ((error = Tcl_GetIntFromObj(interp, objv[4],
+ &handler->priority)) != TCL_OK) {
+ g_free(handler);
+ return error;
+ }
+ if ((error = Tcl_GetIntFromObj(interp, objv[5],
+ &handler->flags)) != TCL_OK) {
+ g_free(handler);
+ return error;
+ }
+ handler->prpl_id = Tcl_GetString(objv[6]);
+ handler->proc = objv[7];
+ handler->helpstr = Tcl_GetString(objv[8]);
+ handler->interp = interp;
+ if ((id = tcl_cmd_register(handler)) == 0) {
+ tcl_cmd_handler_free(handler);
+ Tcl_SetIntObj(result, 0);
+ } else {
+ handler->id = id;
+ Tcl_SetIntObj(result, id);
+ }
+ break;
+ case CMD_CMD_UNREGISTER:
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "id");
+ return TCL_ERROR;
+ }
+ if ((error = Tcl_GetIntFromObj(interp, objv[2],
+ (int *)&id)) != TCL_OK)
+ return error;
+ tcl_cmd_unregister(id, interp);
+ break;
+ }
+
+ return TCL_OK;
+}
+
+int tcl_cmd_connection(ClientData unused, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
+{
+ Tcl_Obj *result = Tcl_GetObjResult(interp), *list, *elem;
+ const char *cmds[] = { "account", "displayname", "handle", "list", NULL };
+ enum { CMD_CONN_ACCOUNT, CMD_CONN_DISPLAYNAME, CMD_CONN_HANDLE, CMD_CONN_LIST } cmd;
+ int error;
+ GList *cur;
+ GaimConnection *gc;
+
+ if (objc < 2) {
+ Tcl_WrongNumArgs(interp, 1, objv, "subcommand ?args?");
+ return TCL_ERROR;
+ }
+
+ if ((error = Tcl_GetIndexFromObj(interp, objv[1], cmds, "subcommand", 0, (int *)&cmd)) != TCL_OK)
+ return error;
+
+ switch (cmd) {
+ case CMD_CONN_ACCOUNT:
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "gc");
+ return TCL_ERROR;
+ }
+ if ((gc = tcl_validate_gc(objv[2], interp)) == NULL)
+ return TCL_ERROR;
+ Tcl_SetObjResult(interp,
+ gaim_tcl_ref_new(GaimTclRefAccount,
+ gaim_connection_get_account(gc)));
+ break;
+ case CMD_CONN_DISPLAYNAME:
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "gc");
+ return TCL_ERROR;
+ }
+ if ((gc = tcl_validate_gc(objv[2], interp)) == NULL)
+ return TCL_ERROR;
+ Tcl_SetStringObj(result, (char *)gaim_connection_get_display_name(gc), -1);
+ break;
+ case CMD_CONN_HANDLE:
+ if (objc != 2) {
+ Tcl_WrongNumArgs(interp, 2, objv, "");
+ return TCL_ERROR;
+ }
+ Tcl_SetIntObj(result, (int)gaim_connections_get_handle());
+ break;
+ case CMD_CONN_LIST:
+ if (objc != 2) {
+ Tcl_WrongNumArgs(interp, 2, objv, "");
+ return TCL_ERROR;
+ }
+ list = Tcl_NewListObj(0, NULL);
+ for (cur = gaim_connections_get_all(); cur != NULL; cur = g_list_next(cur)) {
+ elem = gaim_tcl_ref_new(GaimTclRefConnection, cur->data);
+ Tcl_ListObjAppendElement(interp, list, elem);
+ }
+ Tcl_SetObjResult(interp, list);
+ break;
+ }
+
+ return TCL_OK;
+}
+
+int tcl_cmd_conversation(ClientData unused, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
+{
+ Tcl_Obj *list, *elem, *result = Tcl_GetObjResult(interp);
+ const char *cmds[] = { "find", "handle", "list", "new", "write", "name", "title", "send", NULL };
+ enum { CMD_CONV_FIND, CMD_CONV_HANDLE, CMD_CONV_LIST, CMD_CONV_NEW, CMD_CONV_WRITE , CMD_CONV_NAME, CMD_CONV_TITLE, CMD_CONV_SEND } cmd;
+ const char *styles[] = { "send", "recv", "system", NULL };
+ enum { CMD_CONV_WRITE_SEND, CMD_CONV_WRITE_RECV, CMD_CONV_WRITE_SYSTEM } style;
+ const char *newopts[] = { "-chat", "-im" };
+ enum { CMD_CONV_NEW_CHAT, CMD_CONV_NEW_IM } newopt;
+ GaimConversation *convo;
+ GaimAccount *account;
+ GaimConversationType type;
+ GList *cur;
+ char *opt, *from, *what;
+ int error, argsused, flags = 0;
+
+ if (objc < 2) {
+ Tcl_WrongNumArgs(interp, 1, objv, "subcommand ?args?");
+ return TCL_ERROR;
+ }
+
+ if ((error = Tcl_GetIndexFromObj(interp, objv[1], cmds, "subcommand", 0, (int *)&cmd)) != TCL_OK)
+ return error;
+
+ switch (cmd) {
+ case CMD_CONV_FIND:
+ if (objc != 4) {
+ Tcl_WrongNumArgs(interp, 2, objv, "account name");
+ return TCL_ERROR;
+ }
+ account = NULL;
+ if ((account = tcl_validate_account(objv[2], interp)) == NULL)
+ return TCL_ERROR;
+ convo = gaim_find_conversation_with_account(GAIM_CONV_TYPE_ANY,
+ Tcl_GetString(objv[3]),
+ account);
+ Tcl_SetObjResult(interp, gaim_tcl_ref_new(GaimTclRefConversation, convo));
+ break;
+ case CMD_CONV_HANDLE:
+ if (objc != 2) {
+ Tcl_WrongNumArgs(interp, 2, objv, "");
+ return TCL_ERROR;
+ }
+ Tcl_SetIntObj(result, (int)gaim_conversations_get_handle());
+ break;
+ case CMD_CONV_LIST:
+ list = Tcl_NewListObj(0, NULL);
+ for (cur = gaim_get_conversations(); cur != NULL; cur = g_list_next(cur)) {
+ elem = gaim_tcl_ref_new(GaimTclRefConversation, cur->data);
+ Tcl_ListObjAppendElement(interp, list, elem);
+ }
+ Tcl_SetObjResult(interp, list);
+ break;
+ case CMD_CONV_NEW:
+ if (objc < 4) {
+ Tcl_WrongNumArgs(interp, 2, objv, "?options? account name");
+ return TCL_ERROR;
+ }
+ argsused = 2;
+ type = GAIM_CONV_TYPE_IM;
+ while (argsused < objc) {
+ opt = Tcl_GetString(objv[argsused]);
+ if (*opt == '-') {
+ if ((error = Tcl_GetIndexFromObj(interp, objv[argsused], newopts,
+ "option", 0, (int *)&newopt)) != TCL_OK)
+ return error;
+ argsused++;
+ switch (newopt) {
+ case CMD_CONV_NEW_CHAT:
+ type = GAIM_CONV_TYPE_CHAT;
+ break;
+ case CMD_CONV_NEW_IM:
+ type = GAIM_CONV_TYPE_IM;
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+ if (objc - argsused != 2) {
+ Tcl_WrongNumArgs(interp, 2, objv, "?options? account name");
+ return TCL_ERROR;
+ }
+ if ((account = tcl_validate_account(objv[argsused++], interp)) == NULL)
+ return TCL_ERROR;
+ convo = gaim_conversation_new(type, account, Tcl_GetString(objv[argsused]));
+ Tcl_SetObjResult(interp, gaim_tcl_ref_new(GaimTclRefConversation, convo));
+ break;
+ case CMD_CONV_WRITE:
+ if (objc != 6) {
+ Tcl_WrongNumArgs(interp, 2, objv, "conversation style from what");
+ return TCL_ERROR;
+ }
+ if ((convo = tcl_validate_conversation(objv[2], interp)) == NULL)
+ return TCL_ERROR;
+ if ((error = Tcl_GetIndexFromObj(interp, objv[3], styles, "style", 0, (int *)&style)) != TCL_OK)
+ return error;
+ from = Tcl_GetString(objv[4]);
+ what = Tcl_GetString(objv[5]);
+
+ switch (style) {
+ case CMD_CONV_WRITE_SEND:
+ flags = GAIM_MESSAGE_SEND;
+ break;
+ case CMD_CONV_WRITE_RECV:
+ flags = GAIM_MESSAGE_RECV;
+ break;
+ case CMD_CONV_WRITE_SYSTEM:
+ flags = GAIM_MESSAGE_SYSTEM;
+ break;
+ }
+ if (gaim_conversation_get_type(convo) == GAIM_CONV_TYPE_CHAT)
+ gaim_conv_chat_write(GAIM_CONV_CHAT(convo), from, what, flags, time(NULL));
+ else
+ gaim_conv_im_write(GAIM_CONV_IM(convo), from, what, flags, time(NULL));
+ break;
+ case CMD_CONV_NAME:
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "conversation");
+ return TCL_ERROR;
+ }
+
+ if ((convo = tcl_validate_conversation(objv[2], interp)) == NULL)
+ return TCL_ERROR;
+ Tcl_SetStringObj(result, (char *)gaim_conversation_get_name(convo), -1);
+ break;
+ case CMD_CONV_TITLE:
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "conversation");
+ return TCL_ERROR;
+ }
+
+ if ((convo = tcl_validate_conversation(objv[2], interp)) == NULL)
+ return TCL_ERROR;
+ Tcl_SetStringObj(result, (char *)gaim_conversation_get_title(convo), -1);
+ break;
+ case CMD_CONV_SEND:
+ if (objc != 4) {
+ Tcl_WrongNumArgs(interp, 2, objv, "conversation message");
+ return TCL_ERROR;
+ }
+ if ((convo = tcl_validate_conversation(objv[2], interp)) == NULL)
+ return TCL_ERROR;
+ what = Tcl_GetString(objv[3]);
+ if (gaim_conversation_get_type(convo) == GAIM_CONV_TYPE_CHAT)
+ gaim_conv_chat_send(GAIM_CONV_CHAT(convo), what);
+ else
+ gaim_conv_im_send(GAIM_CONV_IM(convo), what);
+ break;
+ }
+
+ return TCL_OK;
+}
+
+int tcl_cmd_core(ClientData unused, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
+{
+ Tcl_Obj *result = Tcl_GetObjResult(interp);
+ const char *cmds[] = { "handle", "quit", NULL };
+ enum { CMD_CORE_HANDLE, CMD_CORE_QUIT } cmd;
+ int error;
+
+ if (objc < 2) {
+ Tcl_WrongNumArgs(interp, 1, objv, "subcommand ?args?");
+ return TCL_ERROR;
+ }
+
+ if ((error = Tcl_GetIndexFromObj(interp, objv[1], cmds, "subcommand", 0, (int *)&cmd)) != TCL_OK)
+ return error;
+
+ switch (cmd) {
+ case CMD_CORE_HANDLE:
+ if (objc != 2) {
+ Tcl_WrongNumArgs(interp, 2, objv, "");
+ return TCL_ERROR;
+ }
+ Tcl_SetIntObj(result, (int)gaim_get_core());
+ break;
+ case CMD_CORE_QUIT:
+ if (objc != 2) {
+ Tcl_WrongNumArgs(interp, 2, objv, "");
+ return TCL_ERROR;
+ }
+ gaim_core_quit();
+ break;
+ }
+
+ return TCL_OK;
+}
+
+int tcl_cmd_debug(ClientData unused, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
+{
+ char *category, *message;
+ int lev;
+ const char *levels[] = { "-misc", "-info", "-warning", "-error", NULL };
+ GaimDebugLevel levelind[] = { GAIM_DEBUG_MISC, GAIM_DEBUG_INFO, GAIM_DEBUG_WARNING, GAIM_DEBUG_ERROR };
+ int error;
+
+ if (objc != 4) {
+ Tcl_WrongNumArgs(interp, 1, objv, "level category message");
+ return TCL_ERROR;
+ }
+
+ error = Tcl_GetIndexFromObj(interp, objv[1], levels, "debug level", 0, &lev);
+ if (error != TCL_OK)
+ return error;
+
+ category = Tcl_GetString(objv[2]);
+ message = Tcl_GetString(objv[3]);
+
+ gaim_debug(levelind[lev], category, "%s\n", message);
+
+ return TCL_OK;
+}
+
+int tcl_cmd_notify(ClientData unused, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
+{
+ int error, type;
+ const char *opts[] = { "-error", "-warning", "-info", NULL };
+ GaimNotifyMsgType optind[] = { GAIM_NOTIFY_MSG_ERROR, GAIM_NOTIFY_MSG_WARNING, GAIM_NOTIFY_MSG_INFO };
+ char *title, *msg1, *msg2;
+
+ if (objc < 4 || objc > 5) {
+ Tcl_WrongNumArgs(interp, 1, objv, "?type? title primary secondary");
+ return TCL_ERROR;
+ }
+
+ if (objc == 4) {
+ type = 1; /* Default to warning */
+ title = Tcl_GetString(objv[1]);
+ msg1 = Tcl_GetString(objv[2]);
+ msg2 = Tcl_GetString(objv[3]);
+ } else {
+ error = Tcl_GetIndexFromObj(interp, objv[1], opts, "message type", 0, &type);
+ if (error != TCL_OK)
+ return error;
+ title = Tcl_GetString(objv[2]);
+ msg1 = Tcl_GetString(objv[3]);
+ msg2 = Tcl_GetString(objv[4]);
+ }
+
+ gaim_notify_message(_tcl_plugin, optind[type], title, msg1, msg2, NULL, NULL);
+
+ return TCL_OK;
+}
+
+int tcl_cmd_prefs(ClientData unused, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
+{
+ Tcl_Obj *result, *list, *elem, **elems;
+ const char *cmds[] = { "get", "set", "type", NULL };
+ enum { CMD_PREFS_GET, CMD_PREFS_SET, CMD_PREFS_TYPE } cmd;
+ /* char *types[] = { "none", "boolean", "int", "string", "stringlist", NULL }; */
+ /* enum { TCL_PREFS_NONE, TCL_PREFS_BOOL, TCL_PREFS_INT, TCL_PREFS_STRING, TCL_PREFS_STRINGLIST } type; */
+ GaimPrefType preftype;
+ GList *cur;
+ int error, intval, nelem, i;
+
+ if (objc < 2) {
+ Tcl_WrongNumArgs(interp, 1, objv, "subcommand ?args?");
+ return TCL_ERROR;
+ }
+
+ if ((error = Tcl_GetIndexFromObj(interp, objv[1], cmds, "subcommand", 0, (int *)&cmd)) != TCL_OK)
+ return error;
+
+ result = Tcl_GetObjResult(interp);
+ switch (cmd) {
+ case CMD_PREFS_GET:
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 1, objv, "path");
+ return TCL_ERROR;
+ }
+ preftype = gaim_prefs_get_type(Tcl_GetString(objv[2]));
+ switch (preftype) {
+ case GAIM_PREF_NONE:
+ Tcl_SetStringObj(result, "pref type none", -1);
+ return TCL_ERROR;
+ break;
+ case GAIM_PREF_BOOLEAN:
+ Tcl_SetBooleanObj(result, gaim_prefs_get_bool(Tcl_GetString(objv[2])));
+ break;
+ case GAIM_PREF_INT:
+ Tcl_SetIntObj(result, gaim_prefs_get_int(Tcl_GetString(objv[2])));
+ break;
+ case GAIM_PREF_STRING:
+ Tcl_SetStringObj(result, (char *)gaim_prefs_get_string(Tcl_GetString(objv[2])), -1);
+ break;
+ case GAIM_PREF_STRING_LIST:
+ cur = gaim_prefs_get_string_list(Tcl_GetString(objv[2]));
+ list = Tcl_NewListObj(0, NULL);
+ while (cur != NULL) {
+ elem = Tcl_NewStringObj((char *)cur->data, -1);
+ Tcl_ListObjAppendElement(interp, list, elem);
+ cur = g_list_next(cur);
+ }
+ Tcl_SetObjResult(interp, list);
+ break;
+ default:
+ gaim_debug(GAIM_DEBUG_ERROR, "tcl", "tcl does not know about pref type %d\n", preftype);
+ Tcl_SetStringObj(result, "unknown pref type", -1);
+ return TCL_ERROR;
+ }
+ break;
+ case CMD_PREFS_SET:
+ if (objc != 4) {
+ Tcl_WrongNumArgs(interp, 1, objv, "path value");
+ return TCL_ERROR;
+ }
+ preftype = gaim_prefs_get_type(Tcl_GetString(objv[2]));
+ switch (preftype) {
+ case GAIM_PREF_NONE:
+ Tcl_SetStringObj(result, "bad path or pref type none", -1);
+ return TCL_ERROR;
+ break;
+ case GAIM_PREF_BOOLEAN:
+ if ((error = Tcl_GetBooleanFromObj(interp, objv[3], &intval)) != TCL_OK)
+ return error;
+ gaim_prefs_set_bool(Tcl_GetString(objv[2]), intval);
+ break;
+ case GAIM_PREF_INT:
+ if ((error = Tcl_GetIntFromObj(interp, objv[3], &intval)) != TCL_OK)
+ return error;
+ gaim_prefs_set_int(Tcl_GetString(objv[2]), intval);
+ break;
+ case GAIM_PREF_STRING:
+ gaim_prefs_set_string(Tcl_GetString(objv[2]), Tcl_GetString(objv[3]));
+ break;
+ case GAIM_PREF_STRING_LIST:
+ if ((error = Tcl_ListObjGetElements(interp, objv[3], &nelem, &elems)) != TCL_OK)
+ return error;
+ cur = NULL;
+ for (i = 0; i < nelem; i++) {
+ cur = g_list_append(cur, (gpointer)Tcl_GetString(elems[i]));
+ }
+ gaim_prefs_set_string_list(Tcl_GetString(objv[2]), cur);
+ g_list_free(cur);
+ break;
+ default:
+ gaim_debug(GAIM_DEBUG_ERROR, "tcl", "tcl does not know about pref type %d\n", preftype);
+ return TCL_ERROR;
+ }
+ break;
+ case CMD_PREFS_TYPE:
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 1, objv, "path");
+ return TCL_ERROR;
+ }
+ preftype = gaim_prefs_get_type(Tcl_GetString(objv[2]));
+ switch (preftype) {
+ case GAIM_PREF_NONE:
+ Tcl_SetStringObj(result, "none", -1);
+ break;
+ case GAIM_PREF_BOOLEAN:
+ Tcl_SetStringObj(result, "boolean", -1);
+ break;
+ case GAIM_PREF_INT:
+ Tcl_SetStringObj(result, "int", -1);
+ break;
+ case GAIM_PREF_STRING:
+ Tcl_SetStringObj(result, "string", -1);
+ break;
+ case GAIM_PREF_STRING_LIST:
+ Tcl_SetStringObj(result, "stringlist", -1);
+ break;
+ default:
+ gaim_debug(GAIM_DEBUG_ERROR, "tcl", "tcl does not know about pref type %d\n", preftype);
+ Tcl_SetStringObj(result, "unknown", -1);
+ }
+ break;
+ }
+
+ return TCL_OK;
+}
+
+int tcl_cmd_presence(ClientData unused, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
+{
+ const char *cmds[] = { "account", "active_status", "available",
+ "chat_user", "context", "conversation", "idle",
+ "login", "online", "status", "statuses", NULL };
+ enum { CMD_PRESENCE_ACCOUNT, CMD_PRESENCE_ACTIVE_STATUS,
+ CMD_PRESENCE_AVAILABLE, CMD_PRESENCE_CHAT_USER,
+ CMD_PRESENCE_CONTEXT, CMD_PRESENCE_CONVERSATION,
+ CMD_PRESENCE_IDLE, CMD_PRESENCE_LOGIN, CMD_PRESENCE_ONLINE,
+ CMD_PRESENCE_STATUS, CMD_PRESENCE_STATUSES } cmd;
+ Tcl_Obj *result = Tcl_GetObjResult(interp);
+ Tcl_Obj *list, *elem;
+ GaimPresence *presence;
+ const GList *cur;
+ int error, idle, idle_time, login_time;
+
+ if (objc < 2) {
+ Tcl_WrongNumArgs(interp, 1, objv, "subcommand ?args?");
+ return TCL_ERROR;
+ }
+
+ if ((error = Tcl_GetIndexFromObj(interp, objv[1], cmds, "subcommand", 0, (int *)&cmd)) != TCL_OK)
+ return error;
+
+ switch (cmd) {
+ case CMD_PRESENCE_ACCOUNT:
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "presence");
+ return TCL_ERROR;
+ }
+ if ((presence = gaim_tcl_ref_get(interp, objv[2], GaimTclRefPresence)) == NULL)
+ return TCL_ERROR;
+ Tcl_SetObjResult(interp, gaim_tcl_ref_new(GaimTclRefAccount,
+ gaim_presence_get_account(presence)));
+ break;
+ case CMD_PRESENCE_ACTIVE_STATUS:
+ if (objc != 3 && objc != 4 && objc != 5) {
+ Tcl_WrongNumArgs(interp, 2, objv, "presence [?status_id? | ?-primitive primitive?]");
+ return TCL_ERROR;
+ }
+ if ((presence = gaim_tcl_ref_get(interp, objv[2], GaimTclRefPresence)) == NULL)
+ return TCL_ERROR;
+ if (objc == 3) {
+ Tcl_SetObjResult(interp,
+ gaim_tcl_ref_new(GaimTclRefStatus,
+ gaim_presence_get_active_status(presence)));
+ } else if (objc == 4) {
+ Tcl_SetBooleanObj(result,
+ gaim_presence_is_status_active(presence,
+ Tcl_GetString(objv[3])));
+ } else {
+ GaimStatusPrimitive primitive;
+ if (strcmp(Tcl_GetString(objv[3]), "-primitive")) {
+ Tcl_SetStringObj(result, "bad option \"", -1);
+ Tcl_AppendObjToObj(result, objv[3]);
+ Tcl_AppendToObj(result,
+ "\": should be -primitive", -1);
+ return TCL_ERROR;
+ }
+ primitive = gaim_primitive_get_type_from_id(Tcl_GetString(objv[4]));
+ if (primitive == GAIM_STATUS_UNSET) {
+ Tcl_SetStringObj(result, "invalid primitive ", -1);
+ Tcl_AppendObjToObj(result, objv[4]);
+ return TCL_ERROR;
+ }
+ Tcl_SetBooleanObj(result, gaim_presence_is_status_primitive_active(presence, primitive));
+ break;
+ }
+ break;
+ case CMD_PRESENCE_AVAILABLE:
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "presence");
+ return TCL_ERROR;
+ }
+ if ((presence = gaim_tcl_ref_get(interp, objv[2], GaimTclRefPresence)) == NULL)
+ return TCL_ERROR;
+ Tcl_SetBooleanObj(result, gaim_presence_is_available(presence));
+ break;
+ case CMD_PRESENCE_CHAT_USER:
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "presence");
+ return TCL_ERROR;
+ }
+ if ((presence = gaim_tcl_ref_get(interp, objv[2], GaimTclRefPresence)) == NULL)
+ return TCL_ERROR;
+ Tcl_SetStringObj(result, gaim_presence_get_chat_user(presence), -1);
+ break;
+ case CMD_PRESENCE_CONTEXT:
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "presence");
+ return TCL_ERROR;
+ }
+ if ((presence = gaim_tcl_ref_get(interp, objv[2], GaimTclRefPresence)) == NULL)
+ return TCL_ERROR;
+ switch (gaim_presence_get_context(presence)) {
+ case GAIM_PRESENCE_CONTEXT_UNSET:
+ Tcl_SetStringObj(result, "unset", -1);
+ break;
+ case GAIM_PRESENCE_CONTEXT_ACCOUNT:
+ Tcl_SetStringObj(result, "account", -1);
+ break;
+ case GAIM_PRESENCE_CONTEXT_CONV:
+ Tcl_SetStringObj(result, "conversation", -1);
+ break;
+ case GAIM_PRESENCE_CONTEXT_BUDDY:
+ Tcl_SetStringObj(result, "buddy", -1);
+ break;
+ }
+ break;
+ case CMD_PRESENCE_CONVERSATION:
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "presence");
+ return TCL_ERROR;
+ }
+ if ((presence = gaim_tcl_ref_get(interp, objv[2], GaimTclRefPresence)) == NULL)
+ return TCL_ERROR;
+ Tcl_SetObjResult(interp, gaim_tcl_ref_new(GaimTclRefConversation,
+ gaim_presence_get_conversation(presence)));
+ break;
+ case CMD_PRESENCE_IDLE:
+ if (objc < 3 || objc > 5) {
+ Tcl_WrongNumArgs(interp, 2, objv, "presence ?idle? ?time?");
+ return TCL_ERROR;
+ }
+ if ((presence = gaim_tcl_ref_get(interp, objv[2], GaimTclRefPresence)) == NULL)
+ return TCL_ERROR;
+ if (objc == 3) {
+ if (gaim_presence_is_idle(presence)) {
+ idle_time = gaim_presence_get_idle_time (presence);
+ Tcl_SetIntObj(result, idle_time);
+ } else {
+ result = Tcl_NewListObj(0, NULL);
+ Tcl_SetObjResult(interp, result);
+ }
+ break;
+ }
+ if ((error = Tcl_GetBooleanFromObj(interp, objv[3], &idle)) != TCL_OK)
+ return TCL_ERROR;
+ if (objc == 4) {
+ gaim_presence_set_idle(presence, idle, time(NULL));
+ } else if (objc == 5) {
+ if ((error = Tcl_GetIntFromObj(interp,
+ objv[4],
+ &idle_time)) != TCL_OK)
+ return TCL_ERROR;
+ gaim_presence_set_idle(presence, idle, idle_time);
+ }
+ break;
+ case CMD_PRESENCE_LOGIN:
+ if (objc != 3 && objc != 4) {
+ Tcl_WrongNumArgs(interp, 2, objv, "presence ?time?");
+ return TCL_ERROR;
+ }
+ if ((presence = gaim_tcl_ref_get(interp, objv[2], GaimTclRefPresence)) == NULL)
+ return TCL_ERROR;
+ if (objc == 3) {
+ Tcl_SetIntObj(result, gaim_presence_get_login_time(presence));
+ } else {
+ if ((error == Tcl_GetIntFromObj(interp,
+ objv[3],
+ &login_time)) != TCL_OK)
+ return TCL_ERROR;
+ gaim_presence_set_login_time(presence, login_time);
+ }
+ break;
+ case CMD_PRESENCE_ONLINE:
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "presence");
+ return TCL_ERROR;
+ }
+ if ((presence = gaim_tcl_ref_get(interp, objv[2], GaimTclRefPresence)) == NULL)
+ return TCL_ERROR;
+ Tcl_SetBooleanObj(result, gaim_presence_is_online(presence));
+ break;
+ case CMD_PRESENCE_STATUS:
+ if (objc != 4) {
+ Tcl_WrongNumArgs(interp, 2, objv, "presence status_id");
+ return TCL_ERROR;
+ }
+ if ((presence = gaim_tcl_ref_get(interp, objv[2], GaimTclRefPresence)) == NULL)
+ return TCL_ERROR;
+ Tcl_SetObjResult(interp,
+ gaim_tcl_ref_new(GaimTclRefStatus,
+ gaim_presence_get_status(presence,
+ Tcl_GetString(objv[3]))));
+ break;
+ case CMD_PRESENCE_STATUSES:
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "presence");
+ return TCL_ERROR;
+ }
+ if ((presence = gaim_tcl_ref_get(interp, objv[2], GaimTclRefPresence)) == NULL)
+ return TCL_ERROR;
+ list = Tcl_NewListObj(0, NULL);
+ for (cur = gaim_presence_get_statuses(presence); cur != NULL;
+ cur = g_list_next(cur)) {
+ elem = gaim_tcl_ref_new(GaimTclRefStatus, cur->data);
+ Tcl_ListObjAppendElement(interp, list, elem);
+ }
+ Tcl_SetObjResult(interp, list);
+ break;
+ }
+
+ return TCL_OK;
+}
+
+int tcl_cmd_send_im(ClientData unused, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
+{
+ GaimConnection *gc;
+ char *who, *text;
+
+ if (objc != 4) {
+ Tcl_WrongNumArgs(interp, 1, objv, "gc who text");
+ return TCL_ERROR;
+ }
+
+ if ((gc = tcl_validate_gc(objv[1], interp)) == NULL)
+ return TCL_ERROR;
+
+ who = Tcl_GetString(objv[2]);
+ text = Tcl_GetString(objv[3]);
+
+ serv_send_im(gc, who, text, 0);
+
+ return TCL_OK;
+}
+
+int tcl_cmd_signal(ClientData unused, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
+{
+ const char *cmds[] = { "connect", "disconnect", NULL };
+ enum { CMD_SIGNAL_CONNECT, CMD_SIGNAL_DISCONNECT } cmd;
+ struct tcl_signal_handler *handler;
+ Tcl_Obj *result = Tcl_GetObjResult(interp);
+ void *instance;
+ int error;
+
+ if (objc < 2) {
+ Tcl_WrongNumArgs(interp, 1, objv, "subcommand ?args?");
+ return TCL_ERROR;
+ }
+
+ if ((error = Tcl_GetIndexFromObj(interp, objv[1], cmds, "subcommand", 0, (int *)&cmd)) != TCL_OK)
+ return error;
+
+ switch (cmd) {
+ case CMD_SIGNAL_CONNECT:
+ if (objc != 6) {
+ Tcl_WrongNumArgs(interp, 2, objv, "instance signal args proc");
+ return TCL_ERROR;
+ }
+ handler = g_new0(struct tcl_signal_handler, 1);
+ if ((error = Tcl_GetIntFromObj(interp, objv[2], (int *)&handler->instance)) != TCL_OK) {
+ g_free(handler);
+ return error;
+ }
+ handler->signal = objv[3];
+ Tcl_IncrRefCount(handler->signal);
+ handler->args = objv[4];
+ handler->proc = objv[5];
+ handler->interp = interp;
+ if (!tcl_signal_connect(handler)) {
+ tcl_signal_handler_free(handler);
+ Tcl_SetIntObj(result, 1);
+ } else {
+ Tcl_SetIntObj(result, 0);
+ }
+ break;
+ case CMD_SIGNAL_DISCONNECT:
+ if (objc != 4) {
+ Tcl_WrongNumArgs(interp, 2, objv, "instance signal");
+ return TCL_ERROR;
+ }
+ if ((error = Tcl_GetIntFromObj(interp, objv[2], (int *)&instance)) != TCL_OK)
+ return error;
+ tcl_signal_disconnect(instance, Tcl_GetString(objv[3]), interp);
+ break;
+ }
+
+ return TCL_OK;
+}
+
+int tcl_cmd_status(ClientData unused, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
+{
+ const char *cmds[] = { "attr", "type", NULL };
+ enum { CMD_STATUS_ATTR, CMD_STATUS_TYPE } cmd;
+ Tcl_Obj *result = Tcl_GetObjResult(interp);
+ GaimStatus *status;
+ GaimStatusType *status_type;
+ GaimValue *value;
+ const char *attr;
+ int error, v;
+
+ if (objc < 2) {
+ Tcl_WrongNumArgs(interp, 1, objv, "subcommand ?args?");
+ return TCL_ERROR;
+ }
+
+ if ((error = Tcl_GetIndexFromObj(interp, objv[1], cmds, "subcommand", 0, (int *)&cmd)) != TCL_OK)
+ return error;
+
+ switch (cmd) {
+ case CMD_STATUS_ATTR:
+ if (objc != 4 && objc != 5) {
+ Tcl_WrongNumArgs(interp, 2, objv, "status attr_id ?value?");
+ return TCL_ERROR;
+ }
+ if ((status = gaim_tcl_ref_get(interp, objv[2], GaimTclRefStatus)) == NULL)
+ return TCL_ERROR;
+ attr = Tcl_GetString(objv[3]);
+ value = gaim_status_get_attr_value(status, attr);
+ if (value == NULL) {
+ Tcl_SetStringObj(result, "no such attribute", -1);
+ return TCL_ERROR;
+ }
+ switch (gaim_value_get_type(value)) {
+ case GAIM_TYPE_BOOLEAN:
+ if (objc == 4) {
+ Tcl_SetBooleanObj(result, gaim_value_get_boolean(value));
+ } else {
+ if ((error = Tcl_GetBooleanFromObj(interp, objv[4], &v)) != TCL_OK)
+ return error;
+ gaim_status_set_attr_boolean(status, attr, v);
+ }
+ break;
+ case GAIM_TYPE_INT:
+ if (objc == 4) {
+ Tcl_SetIntObj(result, gaim_value_get_int(value));
+ } else {
+ if ((error = Tcl_GetIntFromObj(interp, objv[4], &v)) != TCL_OK)
+ return error;
+ gaim_status_set_attr_int(status, attr, v );
+ }
+ break;
+ case GAIM_TYPE_STRING:
+ if (objc == 4)
+ Tcl_SetStringObj(result, gaim_value_get_string(value), -1);
+ else
+ gaim_status_set_attr_string(status, attr, Tcl_GetString(objv[4]));
+ break;
+ default:
+ Tcl_SetStringObj(result, "attribute has unknown type", -1);
+ return TCL_ERROR;
+ }
+ break;
+ case CMD_STATUS_TYPE:
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "status");
+ return TCL_ERROR;
+ }
+ if ((status = gaim_tcl_ref_get(interp, objv[2], GaimTclRefStatus)) == NULL)
+ return TCL_ERROR;
+ status_type = gaim_status_get_type(status);
+ Tcl_SetObjResult(interp, gaim_tcl_ref_new(GaimTclRefStatusType,
+ status_type));
+ break;
+ }
+
+ return TCL_OK;
+}
+
+int tcl_cmd_status_attr(ClientData unused, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
+{
+ const char *cmds[] = { "id", "name", NULL };
+ enum { CMD_STATUS_ATTR_ID, CMD_STATUS_ATTR_NAME } cmd;
+ Tcl_Obj *result = Tcl_GetObjResult(interp);
+ GaimStatusAttr *attr;
+ int error;
+
+ if (objc < 2) {
+ Tcl_WrongNumArgs(interp, 1, objv, "subcommand ?args?");
+ return TCL_ERROR;
+ }
+
+ if ((error = Tcl_GetIndexFromObj(interp, objv[1], cmds, "subcommand", 0, (int *)&cmd)) != TCL_OK)
+ return error;
+
+ switch (cmd) {
+ case CMD_STATUS_ATTR_ID:
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "attr");
+ return TCL_ERROR;
+ }
+ if ((attr = gaim_tcl_ref_get(interp, objv[2], GaimTclRefStatusAttr)) == NULL)
+ return TCL_ERROR;
+ Tcl_SetStringObj(result, gaim_status_attr_get_id(attr), -1);
+ break;
+ case CMD_STATUS_ATTR_NAME:
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "attr");
+ return TCL_ERROR;
+ }
+ if ((attr = gaim_tcl_ref_get(interp, objv[2], GaimTclRefStatusAttr)) == NULL)
+ return TCL_ERROR;
+ Tcl_SetStringObj(result, gaim_status_attr_get_name(attr), -1);
+ break;
+ }
+
+ return TCL_OK;
+}
+
+int tcl_cmd_status_type(ClientData unused, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
+{
+ const char *cmds[] = { "attr", "attrs", "available", "exclusive", "id",
+ "independent", "name", "primary_attr",
+ "primitive", "saveable", "user_settable",
+ NULL };
+ enum { CMD_STATUS_TYPE_ATTR, CMD_STATUS_TYPE_ATTRS,
+ CMD_STATUS_TYPE_AVAILABLE, CMD_STATUS_TYPE_EXCLUSIVE,
+ CMD_STATUS_TYPE_ID, CMD_STATUS_TYPE_INDEPENDENT,
+ CMD_STATUS_TYPE_NAME, CMD_STATUS_TYPE_PRIMARY_ATTR,
+ CMD_STATUS_TYPE_PRIMITIVE, CMD_STATUS_TYPE_SAVEABLE,
+ CMD_STATUS_TYPE_USER_SETTABLE } cmd;
+ Tcl_Obj *result = Tcl_GetObjResult(interp);
+ GaimStatusType *status_type;
+ Tcl_Obj *list, *elem;
+ const GList *cur;
+ int error;
+
+ if (objc < 2) {
+ Tcl_WrongNumArgs(interp, 1, objv, "subcommand ?args?");
+ return TCL_ERROR;
+ }
+
+ if ((error = Tcl_GetIndexFromObj(interp, objv[1], cmds, "subcommand", 0, (int *)&cmd)) != TCL_OK)
+ return error;
+
+ switch (cmd) {
+ case CMD_STATUS_TYPE_AVAILABLE:
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "statustype");
+ return TCL_ERROR;
+ }
+ if ((status_type = gaim_tcl_ref_get(interp, objv[2], GaimTclRefStatusType)) == NULL)
+ return TCL_ERROR;
+ Tcl_SetBooleanObj(result, gaim_status_type_is_available(status_type));
+ break;
+ case CMD_STATUS_TYPE_ATTR:
+ if (objc != 4) {
+ Tcl_WrongNumArgs(interp, 2, objv, "statustype attr");
+ return TCL_ERROR;
+ }
+ if ((status_type = gaim_tcl_ref_get(interp, objv[2], GaimTclRefStatusType)) == NULL)
+ return TCL_ERROR;
+ Tcl_SetObjResult(interp,
+ gaim_tcl_ref_new(GaimTclRefStatusAttr,
+ gaim_status_type_get_attr(status_type,
+ Tcl_GetStringFromObj(objv[3], NULL))));
+ break;
+ case CMD_STATUS_TYPE_ATTRS:
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "statustype");
+ return TCL_ERROR;
+ }
+ if ((status_type = gaim_tcl_ref_get(interp, objv[2], GaimTclRefStatusType)) == NULL)
+ return TCL_ERROR;
+ list = Tcl_NewListObj(0, NULL);
+ for (cur = gaim_status_type_get_attrs(status_type);
+ cur != NULL; cur = g_list_next(cur)) {
+ elem = gaim_tcl_ref_new(GaimTclRefStatusAttr, cur->data);
+ Tcl_ListObjAppendElement(interp, list, elem);
+ }
+ Tcl_SetObjResult(interp, list);
+ break;
+ case CMD_STATUS_TYPE_EXCLUSIVE:
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "statustype");
+ return TCL_ERROR;
+ }
+ if ((status_type = gaim_tcl_ref_get(interp, objv[2], GaimTclRefStatusType)) == NULL)
+ return TCL_ERROR;
+ Tcl_SetBooleanObj(result, gaim_status_type_is_exclusive(status_type));
+ break;
+ case CMD_STATUS_TYPE_ID:
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "statustype");
+ return TCL_ERROR;
+ }
+ if ((status_type = gaim_tcl_ref_get(interp, objv[2], GaimTclRefStatusType)) == NULL)
+ return TCL_ERROR;
+ Tcl_SetStringObj(result, gaim_status_type_get_id(status_type), -1);
+ break;
+ case CMD_STATUS_TYPE_INDEPENDENT:
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "statustype");
+ return TCL_ERROR;
+ }
+ if ((status_type = gaim_tcl_ref_get(interp, objv[2], GaimTclRefStatusType)) == NULL)
+ return TCL_ERROR;
+ Tcl_SetBooleanObj(result, gaim_status_type_is_independent(status_type));
+ break;
+ case CMD_STATUS_TYPE_NAME:
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "statustype");
+ return TCL_ERROR;
+ }
+ if ((status_type = gaim_tcl_ref_get(interp, objv[2], GaimTclRefStatusType)) == NULL)
+ return TCL_ERROR;
+ Tcl_SetStringObj(result, gaim_status_type_get_name(status_type), -1);
+ break;
+ case CMD_STATUS_TYPE_PRIMITIVE:
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "statustype");
+ return TCL_ERROR;
+ }
+ if ((status_type = gaim_tcl_ref_get(interp, objv[2], GaimTclRefStatusType)) == NULL)
+ return TCL_ERROR;
+ Tcl_SetStringObj(result, gaim_primitive_get_id_from_type(gaim_status_type_get_primitive(status_type)), -1);
+ break;
+ case CMD_STATUS_TYPE_PRIMARY_ATTR:
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "statustype");
+ return TCL_ERROR;
+ }
+ if ((status_type = gaim_tcl_ref_get(interp, objv[2], GaimTclRefStatusType)) == NULL)
+ return TCL_ERROR;
+ Tcl_SetStringObj(result, gaim_status_type_get_primary_attr(status_type), -1);
+ break;
+ case CMD_STATUS_TYPE_SAVEABLE:
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "statustype");
+ return TCL_ERROR;
+ }
+ if ((status_type = gaim_tcl_ref_get(interp, objv[2], GaimTclRefStatusType)) == NULL)
+ return TCL_ERROR;
+ Tcl_SetBooleanObj(result, gaim_status_type_is_saveable(status_type));
+ break;
+ case CMD_STATUS_TYPE_USER_SETTABLE:
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "statustype");
+ return TCL_ERROR;
+ }
+ if ((status_type = gaim_tcl_ref_get(interp, objv[2], GaimTclRefStatusType)) == NULL)
+ return TCL_ERROR;
+ Tcl_SetBooleanObj(result, gaim_status_type_is_user_settable(status_type));
+ break;
+ }
+
+ return TCL_OK;
+}
+
+static gboolean unload_self(gpointer data)
+{
+ GaimPlugin *plugin = data;
+ gaim_plugin_unload(plugin);
+ return FALSE;
+}
+
+int tcl_cmd_unload(ClientData unused, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
+{
+ GaimPlugin *plugin;
+ if (objc != 1) {
+ Tcl_WrongNumArgs(interp, 1, objv, "");
+ return TCL_ERROR;
+ }
+
+ if ((plugin = tcl_interp_get_plugin(interp)) == NULL) {
+ /* This isn't exactly OK, but heh. What do you do? */
+ return TCL_OK;
+ }
+ /* We can't unload immediately, but we can unload at the first
+ * known safe opportunity. */
+ g_idle_add(unload_self, (gpointer)plugin);
+
+ return TCL_OK;
+}
diff --git a/libpurple/plugins/tcl/tcl_gaim.h b/libpurple/plugins/tcl/tcl_gaim.h
new file mode 100644
index 0000000000..90afaf245b
--- /dev/null
+++ b/libpurple/plugins/tcl/tcl_gaim.h
@@ -0,0 +1,116 @@
+/**
+ * @file tcl_gaim.h Gaim Tcl definitions
+ *
+ * gaim
+ *
+ * Copyright (C) 2003 Ethan Blanton <eblanton@cs.purdue.edu>
+ *
+ * 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 _GAIM_TCL_GAIM_H_
+#define _GAIM_TCL_GAIM_H_
+
+#include <tcl.h>
+
+#include "internal.h"
+#include "cmds.h"
+#include "plugin.h"
+#include "value.h"
+#include "stringref.h"
+
+struct tcl_signal_handler {
+ Tcl_Obj *signal;
+ Tcl_Interp *interp;
+
+ void *instance;
+ Tcl_Obj *namespace;
+ /* These following two are temporary during setup */
+ Tcl_Obj *args;
+ Tcl_Obj *proc;
+
+ GaimValue *returntype;
+ int nargs;
+ GaimValue **argtypes;
+};
+
+struct tcl_cmd_handler {
+ int id;
+ Tcl_Obj *cmd;
+ Tcl_Interp *interp;
+
+ Tcl_Obj *namespace;
+ /* These are temporary during setup */
+ const char *args;
+ int priority;
+ int flags;
+ const char *prpl_id;
+ Tcl_Obj *proc;
+ const char *helpstr;
+
+ int nargs;
+};
+
+extern GaimPlugin *_tcl_plugin;
+
+/* Capitalized this way because these are "types" */
+extern GaimStringref *GaimTclRefAccount;
+extern GaimStringref *GaimTclRefConnection;
+extern GaimStringref *GaimTclRefConversation;
+extern GaimStringref *GaimTclRefPointer;
+extern GaimStringref *GaimTclRefPlugin;
+extern GaimStringref *GaimTclRefPresence;
+extern GaimStringref *GaimTclRefStatus;
+extern GaimStringref *GaimTclRefStatusAttr;
+extern GaimStringref *GaimTclRefStatusType;
+extern GaimStringref *GaimTclRefXfer;
+
+GaimPlugin *tcl_interp_get_plugin(Tcl_Interp *interp);
+
+void tcl_signal_init(void);
+void tcl_signal_handler_free(struct tcl_signal_handler *handler);
+void tcl_signal_cleanup(Tcl_Interp *interp);
+gboolean tcl_signal_connect(struct tcl_signal_handler *handler);
+void tcl_signal_disconnect(void *instance, const char *signal, Tcl_Interp *interp);
+
+void tcl_cmd_init(void);
+void tcl_cmd_handler_free(struct tcl_cmd_handler *handler);
+void tcl_cmd_cleanup(Tcl_Interp *interp);
+GaimCmdId tcl_cmd_register(struct tcl_cmd_handler *handler);
+void tcl_cmd_unregister(GaimCmdId id, Tcl_Interp *interp);
+
+void gaim_tcl_ref_init(void);
+void *gaim_tcl_ref_get(Tcl_Interp *interp, Tcl_Obj *obj, GaimStringref *type);
+Tcl_Obj *gaim_tcl_ref_new(GaimStringref *type, void *value);
+
+Tcl_ObjCmdProc tcl_cmd_account;
+Tcl_ObjCmdProc tcl_cmd_signal_connect;
+Tcl_ObjCmdProc tcl_cmd_buddy;
+Tcl_ObjCmdProc tcl_cmd_cmd;
+Tcl_ObjCmdProc tcl_cmd_connection;
+Tcl_ObjCmdProc tcl_cmd_conversation;
+Tcl_ObjCmdProc tcl_cmd_core;
+Tcl_ObjCmdProc tcl_cmd_debug;
+Tcl_ObjCmdProc tcl_cmd_notify;
+Tcl_ObjCmdProc tcl_cmd_prefs;
+Tcl_ObjCmdProc tcl_cmd_presence;
+Tcl_ObjCmdProc tcl_cmd_send_im;
+Tcl_ObjCmdProc tcl_cmd_signal;
+Tcl_ObjCmdProc tcl_cmd_status;
+Tcl_ObjCmdProc tcl_cmd_status_attr;
+Tcl_ObjCmdProc tcl_cmd_status_type;
+Tcl_ObjCmdProc tcl_cmd_unload;
+
+#endif /* _GAIM_TCL_GAIM_H_ */
diff --git a/libpurple/plugins/tcl/tcl_glib.c b/libpurple/plugins/tcl/tcl_glib.c
new file mode 100644
index 0000000000..e8878c55b7
--- /dev/null
+++ b/libpurple/plugins/tcl/tcl_glib.c
@@ -0,0 +1,254 @@
+/*
+ * Tcl/Glib glue
+ *
+ * Copyright (C) 2003, 2004, 2006 Ethan Blanton <eblanton@cs.purdue.edu>
+ *
+ * This file is dual-licensed under the two sets of terms below. You may
+ * use, redistribute, or modify it pursuant to either the set of conditions
+ * under "TERMS 1" or "TERMS 2", at your discretion. The DISCLAIMER
+ * applies to both sets of terms.
+ *
+ * TERMS 1
+ *
+ * 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.
+ *
+ * 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
+ *
+ * TERMS 2
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must contain the above copyright
+ * notice and this comment block in their entirety.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice and the text of this comment block in their entirety in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * DISCLAIMER
+ *
+ * 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.
+ */
+
+/*
+ * NOTES
+ *
+ * This file was developed for the Gaim project. It inserts the Tcl
+ * event loop into the glib2 event loop for the purposes of providing
+ * Tcl bindings in a glib2 (e.g. Gtk2) program. To use it, simply
+ * link it into your executable, include tcl_glib.h, and call the
+ * function tcl_glib_init() before creating or using any Tcl
+ * interpreters. Then go ahead and use Tcl, Tk, whatever to your
+ * heart's content.
+ *
+ * BUGS
+ *
+ * tcl_wait_for_event seems to have a bug that makes vwait not work so
+ * well... I'm not sure why, yet, but I haven't put much time into
+ * it. Hopefully I will figure it out soon. In the meantime, this
+ * means that Tk's bgerror function (which is called when there is an
+ * error in a callback function) causes some Bad Mojo -- you should
+ * override it with a function that does not use Tk
+ */
+
+#include <tcl.h>
+#include <glib.h>
+#include <string.h>
+
+#include "tcl_glib.h"
+
+struct tcl_file_handler {
+ int source;
+ int fd;
+ int mask;
+ int pending;
+ Tcl_FileProc *proc;
+ ClientData data;
+};
+
+struct tcl_file_event {
+ Tcl_Event header;
+ int fd;
+};
+
+static guint tcl_timer;
+static gboolean tcl_timer_pending;
+static GHashTable *tcl_file_handlers;
+
+static void tcl_set_timer(Tcl_Time *timePtr);
+static int tcl_wait_for_event(Tcl_Time *timePtr);
+static void tcl_create_file_handler(int fd, int mask, Tcl_FileProc *proc, ClientData data);
+static void tcl_delete_file_handler(int fd);
+
+static gboolean tcl_kick(gpointer data);
+static gboolean tcl_file_callback(GIOChannel *source, GIOCondition condition, gpointer data);
+static int tcl_file_event_callback(Tcl_Event *event, int flags);
+
+#undef Tcl_InitNotifier
+
+ClientData Tcl_InitNotifier()
+{
+ return NULL;
+}
+
+void tcl_glib_init ()
+{
+ Tcl_NotifierProcs notifier;
+
+ memset(&notifier, 0, sizeof(notifier));
+
+ notifier.createFileHandlerProc = tcl_create_file_handler;
+ notifier.deleteFileHandlerProc = tcl_delete_file_handler;
+ notifier.setTimerProc = tcl_set_timer;
+ notifier.waitForEventProc = tcl_wait_for_event;
+
+ Tcl_SetNotifier(&notifier);
+ Tcl_SetServiceMode(TCL_SERVICE_ALL);
+
+ tcl_timer_pending = FALSE;
+ tcl_file_handlers = g_hash_table_new(g_direct_hash, g_direct_equal);
+}
+
+static void tcl_set_timer(Tcl_Time *timePtr)
+{
+ guint interval;
+
+ if (tcl_timer_pending)
+ g_source_remove(tcl_timer);
+
+ if (timePtr == NULL) {
+ tcl_timer_pending = FALSE;
+ return;
+ }
+
+ interval = timePtr->sec * 1000 + (timePtr->usec ? timePtr->usec / 1000 : 0);
+ tcl_timer = g_timeout_add(interval, tcl_kick, NULL);
+ tcl_timer_pending = TRUE;
+}
+
+static int tcl_wait_for_event(Tcl_Time *timePtr)
+{
+ if (!timePtr || (timePtr->sec == 0 && timePtr->usec == 0)) {
+ g_main_context_iteration(NULL, FALSE);
+ return 1;
+ } else {
+ tcl_set_timer(timePtr);
+ }
+
+ g_main_context_iteration(NULL, TRUE);
+
+ return 1;
+}
+
+static void tcl_create_file_handler(int fd, int mask, Tcl_FileProc *proc, ClientData data)
+{
+ struct tcl_file_handler *tfh = g_new0(struct tcl_file_handler, 1);
+ GIOChannel *channel;
+ GIOCondition cond = 0;
+
+ if (g_hash_table_lookup(tcl_file_handlers, GINT_TO_POINTER(fd)))
+ tcl_delete_file_handler(fd);
+
+ if (mask & TCL_READABLE)
+ cond |= G_IO_IN;
+ if (mask & TCL_WRITABLE)
+ cond |= G_IO_OUT;
+ if (mask & TCL_EXCEPTION)
+ cond |= G_IO_ERR|G_IO_HUP|G_IO_NVAL;
+
+ tfh->fd = fd;
+ tfh->mask = mask;
+ tfh->proc = proc;
+ tfh->data = data;
+
+ channel = g_io_channel_unix_new(fd);
+ tfh->source = g_io_add_watch_full(channel, G_PRIORITY_DEFAULT, cond, tcl_file_callback, tfh, g_free);
+ g_io_channel_unref(channel);
+
+ g_hash_table_insert(tcl_file_handlers, GINT_TO_POINTER(fd), tfh);
+
+ Tcl_ServiceAll();
+}
+
+static void tcl_delete_file_handler(int fd)
+{
+ struct tcl_file_handler *tfh = g_hash_table_lookup(tcl_file_handlers, GINT_TO_POINTER(fd));
+
+ if (tfh == NULL)
+ return;
+
+ g_source_remove(tfh->source);
+ g_hash_table_remove(tcl_file_handlers, GINT_TO_POINTER(fd));
+
+ Tcl_ServiceAll();
+}
+
+static gboolean tcl_kick(gpointer data)
+{
+ tcl_timer_pending = FALSE;
+
+ Tcl_ServiceAll();
+
+ return FALSE;
+}
+
+static gboolean tcl_file_callback(GIOChannel *source, GIOCondition condition, gpointer data)
+{
+ struct tcl_file_handler *tfh = data;
+ struct tcl_file_event *fev;
+ int mask = 0;
+
+ if (condition & G_IO_IN)
+ mask |= TCL_READABLE;
+ if (condition & G_IO_OUT)
+ mask |= TCL_WRITABLE;
+ if (condition & (G_IO_ERR|G_IO_HUP|G_IO_NVAL))
+ mask |= TCL_EXCEPTION;
+
+ if (!(tfh->mask & (mask & ~tfh->pending)))
+ return TRUE;
+
+ tfh->pending |= mask;
+ fev = (struct tcl_file_event *)ckalloc(sizeof(struct tcl_file_event));
+ memset(fev, 0, sizeof(struct tcl_file_event));
+ fev->header.proc = tcl_file_event_callback;
+ fev->fd = tfh->fd;
+ Tcl_QueueEvent((Tcl_Event *)fev, TCL_QUEUE_TAIL);
+
+ Tcl_ServiceAll();
+
+ return TRUE;
+}
+
+int tcl_file_event_callback(Tcl_Event *event, int flags)
+{
+ struct tcl_file_handler *tfh;
+ struct tcl_file_event *fev = (struct tcl_file_event *)event;
+ int mask;
+
+ if (!(flags & TCL_FILE_EVENTS)) {
+ return 0;
+ }
+
+ tfh = g_hash_table_lookup(tcl_file_handlers, GINT_TO_POINTER(fev->fd));
+ if (tfh == NULL)
+ return 1;
+
+ mask = tfh->mask & tfh->pending;
+ if (mask)
+ (*tfh->proc)(tfh->data, mask);
+ tfh->pending = 0;
+
+ return 1;
+}
diff --git a/libpurple/plugins/tcl/tcl_glib.h b/libpurple/plugins/tcl/tcl_glib.h
new file mode 100644
index 0000000000..ebe18d89a1
--- /dev/null
+++ b/libpurple/plugins/tcl/tcl_glib.h
@@ -0,0 +1,29 @@
+/*
+ * Tcl/Glib glue
+ *
+ * Copyright (C) 2003 Ethan Blanton <eblanton@cs.purdue.edu>
+ *
+ * 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 _GAIM_TCL_GLIB_H_
+#define _GAIM_TCL_GLIB_H_
+
+#include <tcl.h>
+#include <glib.h>
+
+void tcl_glib_init(void);
+
+#endif /* _GAIM_TCL_GLIB_H_ */
diff --git a/libpurple/plugins/tcl/tcl_ref.c b/libpurple/plugins/tcl/tcl_ref.c
new file mode 100644
index 0000000000..340e68c72b
--- /dev/null
+++ b/libpurple/plugins/tcl/tcl_ref.c
@@ -0,0 +1,152 @@
+/**
+ * @file tcl_ref.c Gaim Tcl typed references API
+ *
+ * gaim
+ *
+ * Copyright (C) 2006 Ethan Blanton <eblanton@cs.purdue.edu>
+ *
+ * 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 <tcl.h>
+#include <glib.h>
+
+#include "tcl_gaim.h"
+#include "stringref.h"
+
+/* Instead of all that internal representation mumbo jumbo, use these
+ * macros to access the internal representation of a GaimTclRef */
+#define OBJ_REF_TYPE(obj) (obj->internalRep.twoPtrValue.ptr1)
+#define OBJ_REF_VALUE(obj) (obj->internalRep.twoPtrValue.ptr2)
+
+static Tcl_FreeInternalRepProc gaim_tcl_ref_free;
+static Tcl_DupInternalRepProc gaim_tcl_ref_dup;
+static Tcl_UpdateStringProc gaim_tcl_ref_update;
+static Tcl_SetFromAnyProc gaim_tcl_ref_set;
+
+static Tcl_ObjType gaim_tcl_ref = {
+ "GaimTclRef",
+ gaim_tcl_ref_free,
+ gaim_tcl_ref_dup,
+ gaim_tcl_ref_update,
+ gaim_tcl_ref_set
+};
+
+void gaim_tcl_ref_init()
+{
+ Tcl_RegisterObjType(&gaim_tcl_ref);
+}
+
+void *gaim_tcl_ref_get(Tcl_Interp *interp, Tcl_Obj *obj, GaimStringref *type)
+{
+ if (obj->typePtr != &gaim_tcl_ref) {
+ if (Tcl_ConvertToType(interp, obj, &gaim_tcl_ref) != TCL_OK)
+ return NULL;
+ }
+ if (strcmp(gaim_stringref_value(OBJ_REF_TYPE(obj)),
+ gaim_stringref_value(type))) {
+ if (interp) {
+ Tcl_Obj *error = Tcl_NewStringObj("Bad Gaim reference type: expected ", -1);
+ Tcl_AppendToObj(error, gaim_stringref_value(type), -1);
+ Tcl_AppendToObj(error, " but got ", -1);
+ Tcl_AppendToObj(error, gaim_stringref_value(OBJ_REF_TYPE(obj)), -1);
+ Tcl_SetObjResult(interp, error);
+ }
+ return NULL;
+ }
+ return OBJ_REF_VALUE(obj);
+}
+
+Tcl_Obj *gaim_tcl_ref_new(GaimStringref *type, void *value)
+{
+ Tcl_Obj *obj = Tcl_NewObj();
+ obj->typePtr = &gaim_tcl_ref;
+ OBJ_REF_TYPE(obj) = gaim_stringref_ref(type);
+ OBJ_REF_VALUE(obj) = value;
+ Tcl_InvalidateStringRep(obj);
+ return obj;
+}
+
+static void gaim_tcl_ref_free(Tcl_Obj *obj)
+{
+ gaim_stringref_unref(OBJ_REF_TYPE(obj));
+}
+
+static void gaim_tcl_ref_dup(Tcl_Obj *obj1, Tcl_Obj *obj2)
+{
+ OBJ_REF_TYPE(obj2) = gaim_stringref_ref(OBJ_REF_TYPE(obj1));
+ OBJ_REF_VALUE(obj2) = OBJ_REF_VALUE(obj1);
+}
+
+static void gaim_tcl_ref_update(Tcl_Obj *obj)
+{
+ /* This is ugly on memory, but we pretty much have to either
+ * do this or guesstimate lengths or introduce a varargs
+ * function in here ... ugh. */
+ char *bytes = g_strdup_printf("gaim-%s:%p",
+ gaim_stringref_value(OBJ_REF_TYPE(obj)),
+ OBJ_REF_VALUE(obj));
+
+ obj->length = strlen(bytes);
+ obj->bytes = ckalloc(obj->length + 1);
+ strcpy(obj->bytes, bytes);
+ g_free(bytes);
+}
+
+/* This isn't as memory-efficient as setting could be, because we
+ * essentially have to synthesize the Stringref here, where we would
+ * really rather dup it. Oh, well. */
+static int gaim_tcl_ref_set(Tcl_Interp *interp, Tcl_Obj *obj)
+{
+ char *bytes = Tcl_GetStringFromObj(obj, NULL);
+ char *ptr;
+ GaimStringref *type;
+ void *value;
+
+ if (strlen(bytes) < 7
+ || strncmp(bytes, "gaim-", 5)
+ || (ptr = strchr(bytes, ':')) == NULL
+ || (ptr - bytes) == 5)
+ goto badobject;
+
+ /* Bad Ethan */
+ *ptr = '\0';
+ type = gaim_stringref_new(bytes + 5);
+ *ptr = ':';
+ ptr++;
+
+ if (sscanf(ptr, "%p", &value) == 0) {
+ gaim_stringref_unref(type);
+ goto badobject;
+ }
+
+ /* At this point we know we have a good object; free the old and
+ * install our internal representation. */
+ if (obj->typePtr != NULL && obj->typePtr->freeIntRepProc != NULL)
+ obj->typePtr->freeIntRepProc(obj);
+
+ obj->typePtr = &gaim_tcl_ref;
+ OBJ_REF_TYPE(obj) = type;
+ OBJ_REF_VALUE(obj) = value;
+
+ return TCL_OK;
+
+badobject:
+ if (interp) {
+ Tcl_SetObjResult(interp,
+ Tcl_NewStringObj("invalid GaimTclRef representation", -1));
+ }
+ return TCL_ERROR;
+}
diff --git a/libpurple/plugins/tcl/tcl_signals.c b/libpurple/plugins/tcl/tcl_signals.c
new file mode 100644
index 0000000000..08c1ea105d
--- /dev/null
+++ b/libpurple/plugins/tcl/tcl_signals.c
@@ -0,0 +1,396 @@
+/**
+ * @file tcl_signals.c Gaim Tcl signal API
+ *
+ * gaim
+ *
+ * Copyright (C) 2003 Ethan Blanton <eblanton@cs.purdue.edu>
+ *
+ * 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 <tcl.h>
+#include <stdarg.h>
+
+#include "tcl_gaim.h"
+
+#include "internal.h"
+#include "connection.h"
+#include "conversation.h"
+#include "signals.h"
+#include "debug.h"
+#include "value.h"
+#include "core.h"
+
+static GList *tcl_callbacks;
+
+static void *tcl_signal_callback(va_list args, struct tcl_signal_handler *handler);
+static Tcl_Obj *new_cb_namespace (void);
+
+void tcl_signal_init()
+{
+ tcl_callbacks = NULL;
+}
+
+void tcl_signal_handler_free(struct tcl_signal_handler *handler)
+{
+ if (handler == NULL)
+ return;
+
+ Tcl_DecrRefCount(handler->signal);
+ if (handler->namespace)
+ Tcl_DecrRefCount(handler->namespace);
+ g_free(handler);
+}
+
+void tcl_signal_cleanup(Tcl_Interp *interp)
+{
+ GList *cur;
+ struct tcl_signal_handler *handler;
+
+ for (cur = tcl_callbacks; cur != NULL; cur = g_list_next(cur)) {
+ handler = cur->data;
+ if (handler->interp == interp) {
+ tcl_signal_handler_free(handler);
+ cur->data = NULL;
+ }
+ }
+ tcl_callbacks = g_list_remove_all(tcl_callbacks, NULL);
+}
+
+gboolean tcl_signal_connect(struct tcl_signal_handler *handler)
+{
+ GString *proc;
+
+ gaim_signal_get_values(handler->instance,
+ Tcl_GetString(handler->signal),
+ &handler->returntype, &handler->nargs,
+ &handler->argtypes);
+
+ tcl_signal_disconnect(handler->interp, Tcl_GetString(handler->signal),
+ handler->interp);
+
+ if (!gaim_signal_connect_vargs(handler->instance,
+ Tcl_GetString(handler->signal),
+ (void *)handler->interp,
+ GAIM_CALLBACK(tcl_signal_callback),
+ (void *)handler))
+ return FALSE;
+
+ handler->namespace = new_cb_namespace ();
+ Tcl_IncrRefCount(handler->namespace);
+ proc = g_string_new("");
+ g_string_append_printf(proc, "namespace eval %s { proc cb { %s } { %s } }",
+ Tcl_GetString(handler->namespace),
+ Tcl_GetString(handler->args),
+ Tcl_GetString(handler->proc));
+ if (Tcl_Eval(handler->interp, proc->str) != TCL_OK) {
+ Tcl_DecrRefCount(handler->namespace);
+ g_string_free(proc, TRUE);
+ return FALSE;
+ }
+ g_string_free(proc, TRUE);
+
+ tcl_callbacks = g_list_append(tcl_callbacks, (gpointer)handler);
+
+ return TRUE;
+}
+
+void tcl_signal_disconnect(void *instance, const char *signal, Tcl_Interp *interp)
+{
+ GList *cur;
+ struct tcl_signal_handler *handler;
+ gboolean found = FALSE;
+ GString *cmd;
+
+ for (cur = tcl_callbacks; cur != NULL; cur = g_list_next(cur)) {
+ handler = cur->data;
+ if (handler->interp == interp && handler->instance == instance
+ && !strcmp(signal, Tcl_GetString(handler->signal))) {
+ gaim_signal_disconnect(instance, signal, handler->interp,
+ GAIM_CALLBACK(tcl_signal_callback));
+ cmd = g_string_sized_new(64);
+ g_string_printf(cmd, "namespace delete %s",
+ Tcl_GetString(handler->namespace));
+ Tcl_EvalEx(interp, cmd->str, -1, TCL_EVAL_GLOBAL);
+ tcl_signal_handler_free(handler);
+ g_string_free(cmd, TRUE);
+ cur->data = NULL;
+ found = TRUE;
+ break;
+ }
+ }
+ if (found)
+ tcl_callbacks = g_list_remove_all(tcl_callbacks, NULL);
+}
+
+static GaimStringref *ref_type(GaimSubType type)
+{
+ switch (type) {
+ case GAIM_SUBTYPE_ACCOUNT:
+ return GaimTclRefAccount;
+ case GAIM_SUBTYPE_CONNECTION:
+ return GaimTclRefConnection;
+ case GAIM_SUBTYPE_CONVERSATION:
+ return GaimTclRefConversation;
+ case GAIM_SUBTYPE_PLUGIN:
+ return GaimTclRefPlugin;
+ case GAIM_SUBTYPE_STATUS:
+ return GaimTclRefStatus;
+ case GAIM_SUBTYPE_XFER:
+ return GaimTclRefXfer;
+ default:
+ return NULL;
+ }
+}
+
+static void *tcl_signal_callback(va_list args, struct tcl_signal_handler *handler)
+{
+ GString *name, *val;
+ GaimBlistNode *node;
+ int error, i;
+ void *retval = NULL;
+ Tcl_Obj *cmd, *arg, *result;
+ void **vals; /* Used for inout parameters */
+ char ***strs;
+
+ vals = g_new0(void *, handler->nargs);
+ strs = g_new0(char **, handler->nargs);
+ name = g_string_sized_new(32);
+ val = g_string_sized_new(32);
+
+ cmd = Tcl_NewListObj(0, NULL);
+ Tcl_IncrRefCount(cmd);
+
+ arg = Tcl_DuplicateObj(handler->namespace);
+ Tcl_AppendStringsToObj(arg, "::cb", NULL);
+ Tcl_ListObjAppendElement(handler->interp, cmd, arg);
+
+ for (i = 0; i < handler->nargs; i++) {
+ if (gaim_value_is_outgoing(handler->argtypes[i]))
+ g_string_printf(name, "%s::arg%d",
+ Tcl_GetString(handler->namespace), i);
+
+ switch(gaim_value_get_type(handler->argtypes[i])) {
+ case GAIM_TYPE_UNKNOWN: /* What? I guess just pass the word ... */
+ /* treat this as a pointer, but complain first */
+ gaim_debug(GAIM_DEBUG_ERROR, "tcl", "unknown GaimValue type %d\n",
+ gaim_value_get_type(handler->argtypes[i]));
+ case GAIM_TYPE_POINTER:
+ case GAIM_TYPE_OBJECT:
+ case GAIM_TYPE_BOXED:
+ /* These are all "pointer" types to us */
+ if (gaim_value_is_outgoing(handler->argtypes[i]))
+ gaim_debug_error("tcl", "pointer types do not currently support outgoing arguments\n");
+ arg = gaim_tcl_ref_new(GaimTclRefPointer, va_arg(args, void *));
+ break;
+ case GAIM_TYPE_BOOLEAN:
+ if (gaim_value_is_outgoing(handler->argtypes[i])) {
+ vals[i] = va_arg(args, gboolean *);
+ Tcl_LinkVar(handler->interp, name->str,
+ (char *)&vals[i], TCL_LINK_BOOLEAN);
+ arg = Tcl_NewStringObj(name->str, -1);
+ } else {
+ arg = Tcl_NewBooleanObj(va_arg(args, gboolean));
+ }
+ break;
+ case GAIM_TYPE_CHAR:
+ case GAIM_TYPE_UCHAR:
+ case GAIM_TYPE_SHORT:
+ case GAIM_TYPE_USHORT:
+ case GAIM_TYPE_INT:
+ case GAIM_TYPE_UINT:
+ case GAIM_TYPE_LONG:
+ case GAIM_TYPE_ULONG:
+ case GAIM_TYPE_ENUM:
+ /* I should really cast these individually to
+ * preserve as much information as possible ...
+ * but heh */
+ if (gaim_value_is_outgoing(handler->argtypes[i])) {
+ vals[i] = va_arg(args, int *);
+ Tcl_LinkVar(handler->interp, name->str,
+ vals[i], TCL_LINK_INT);
+ arg = Tcl_NewStringObj(name->str, -1);
+ } else {
+ arg = Tcl_NewIntObj(va_arg(args, int));
+ }
+ break;
+ case GAIM_TYPE_INT64:
+ case GAIM_TYPE_UINT64:
+ /* Tcl < 8.4 doesn't have wide ints, so we have ugly
+ * ifdefs in here */
+ if (gaim_value_is_outgoing(handler->argtypes[i])) {
+ vals[i] = (void *)va_arg(args, gint64 *);
+ #if (TCL_MAJOR_VERSION >= 8 && TCL_MINOR_VERSION >= 4)
+ Tcl_LinkVar(handler->interp, name->str,
+ vals[i], TCL_LINK_WIDE_INT);
+ #else
+ /* This is going to cause weirdness at best,
+ * but what do you want ... we're losing
+ * precision */
+ Tcl_LinkVar(handler->interp, name->str,
+ vals[i], TCL_LINK_INT);
+ #endif /* Tcl >= 8.4 */
+ arg = Tcl_NewStringObj(name->str, -1);
+ } else {
+ #if (TCL_MAJOR_VERSION >= 8 && TCL_MINOR_VERSION >= 4)
+ arg = Tcl_NewWideIntObj(va_arg(args, gint64));
+ #else
+ arg = Tcl_NewIntObj((int)va_arg(args, int));
+ #endif /* Tcl >= 8.4 */
+ }
+ break;
+ case GAIM_TYPE_STRING:
+ if (gaim_value_is_outgoing(handler->argtypes[i])) {
+ strs[i] = va_arg(args, char **);
+ if (strs[i] == NULL || *strs[i] == NULL) {
+ vals[i] = ckalloc(1);
+ *(char *)vals[i] = '\0';
+ } else {
+ vals[i] = ckalloc(strlen(*strs[i]) + 1);
+ strcpy(vals[i], *strs[i]);
+ }
+ Tcl_LinkVar(handler->interp, name->str,
+ (char *)&vals[i], TCL_LINK_STRING);
+ arg = Tcl_NewStringObj(name->str, -1);
+ } else {
+ arg = Tcl_NewStringObj(va_arg(args, char *), -1);
+ }
+ break;
+ case GAIM_TYPE_SUBTYPE:
+ switch (gaim_value_get_subtype(handler->argtypes[i])) {
+ case GAIM_SUBTYPE_UNKNOWN:
+ gaim_debug(GAIM_DEBUG_ERROR, "tcl", "subtype unknown\n");
+ case GAIM_SUBTYPE_ACCOUNT:
+ case GAIM_SUBTYPE_CONNECTION:
+ case GAIM_SUBTYPE_CONVERSATION:
+ case GAIM_SUBTYPE_STATUS:
+ case GAIM_SUBTYPE_PLUGIN:
+ case GAIM_SUBTYPE_XFER:
+ if (gaim_value_is_outgoing(handler->argtypes[i]))
+ gaim_debug_error("tcl", "pointer subtypes do not currently support outgoing arguments\n");
+ arg = gaim_tcl_ref_new(ref_type(gaim_value_get_subtype(handler->argtypes[i])), va_arg(args, void *));
+ break;
+ case GAIM_SUBTYPE_BLIST:
+ case GAIM_SUBTYPE_BLIST_BUDDY:
+ case GAIM_SUBTYPE_BLIST_GROUP:
+ case GAIM_SUBTYPE_BLIST_CHAT:
+ /* We're going to switch again for code-deduping */
+ if (gaim_value_is_outgoing(handler->argtypes[i]))
+ node = *va_arg(args, GaimBlistNode **);
+ else
+ node = va_arg(args, GaimBlistNode *);
+ switch (node->type) {
+ case GAIM_BLIST_GROUP_NODE:
+ arg = Tcl_NewListObj(0, NULL);
+ Tcl_ListObjAppendElement(handler->interp, arg,
+ Tcl_NewStringObj("group", -1));
+ Tcl_ListObjAppendElement(handler->interp, arg,
+ Tcl_NewStringObj(((GaimGroup *)node)->name, -1));
+ break;
+ case GAIM_BLIST_CONTACT_NODE:
+ /* g_string_printf(val, "contact {%s}", Contact Name? ); */
+ arg = Tcl_NewStringObj("contact", -1);
+ break;
+ case GAIM_BLIST_BUDDY_NODE:
+ arg = Tcl_NewListObj(0, NULL);
+ Tcl_ListObjAppendElement(handler->interp, arg,
+ Tcl_NewStringObj("buddy", -1));
+ Tcl_ListObjAppendElement(handler->interp, arg,
+ Tcl_NewStringObj(((GaimBuddy *)node)->name, -1));
+ Tcl_ListObjAppendElement(handler->interp, arg,
+ gaim_tcl_ref_new(GaimTclRefAccount,
+ ((GaimBuddy *)node)->account));
+ break;
+ case GAIM_BLIST_CHAT_NODE:
+ arg = Tcl_NewListObj(0, NULL);
+ Tcl_ListObjAppendElement(handler->interp, arg,
+ Tcl_NewStringObj("chat", -1));
+ Tcl_ListObjAppendElement(handler->interp, arg,
+ Tcl_NewStringObj(((GaimChat *)node)->alias, -1));
+ Tcl_ListObjAppendElement(handler->interp, arg,
+ gaim_tcl_ref_new(GaimTclRefAccount,
+ ((GaimChat *)node)->account));
+ break;
+ case GAIM_BLIST_OTHER_NODE:
+ arg = Tcl_NewStringObj("other", -1);
+ break;
+ }
+ break;
+ }
+ }
+ Tcl_ListObjAppendElement(handler->interp, cmd, arg);
+ }
+
+ /* Call the friggin' procedure already */
+ if ((error = Tcl_EvalObjEx(handler->interp, cmd, TCL_EVAL_GLOBAL)) != TCL_OK) {
+ gaim_debug(GAIM_DEBUG_ERROR, "tcl", "error evaluating callback: %s\n",
+ Tcl_GetString(Tcl_GetObjResult(handler->interp)));
+ } else {
+ result = Tcl_GetObjResult(handler->interp);
+ /* handle return values -- strings and words only */
+ if (handler->returntype) {
+ if (gaim_value_get_type(handler->returntype) == GAIM_TYPE_STRING) {
+ retval = (void *)g_strdup(Tcl_GetString(result));
+ } else {
+ if ((error = Tcl_GetIntFromObj(handler->interp, result, (int *)&retval)) != TCL_OK) {
+ gaim_debug(GAIM_DEBUG_ERROR, "tcl", "Error retrieving procedure result: %s\n",
+ Tcl_GetString(Tcl_GetObjResult(handler->interp)));
+ retval = NULL;
+ }
+ }
+ }
+ }
+
+ /* And finally clean up */
+ for (i = 0; i < handler->nargs; i++) {
+ g_string_printf(name, "%s::arg%d",
+ Tcl_GetString(handler->namespace), i);
+ if (gaim_value_is_outgoing(handler->argtypes[i])
+ && gaim_value_get_type(handler->argtypes[i]) != GAIM_TYPE_SUBTYPE)
+ Tcl_UnlinkVar(handler->interp, name->str);
+
+ /* We basically only have to deal with strings on the
+ * way out */
+ switch (gaim_value_get_type(handler->argtypes[i])) {
+ case GAIM_TYPE_STRING:
+ if (gaim_value_is_outgoing(handler->argtypes[i])) {
+ if (vals[i] != NULL && *(char **)vals[i] != NULL) {
+ g_free(*strs[i]);
+ *strs[i] = g_strdup(vals[i]);
+ }
+ ckfree(vals[i]);
+ }
+ break;
+ default:
+ /* nothing */
+ ;
+ }
+ }
+
+ g_string_free(name, TRUE);
+ g_string_free(val, TRUE);
+ g_free(vals);
+ g_free(strs);
+
+ return retval;
+}
+
+static Tcl_Obj *new_cb_namespace ()
+{
+ static int cbnum;
+ char name[32];
+
+ g_snprintf (name, sizeof(name), "::gaim::_callback::cb_%d", cbnum++);
+ return Tcl_NewStringObj (name, -1);
+}
diff --git a/libpurple/plugins/test.pl b/libpurple/plugins/test.pl
new file mode 100644
index 0000000000..eea02a46e0
--- /dev/null
+++ b/libpurple/plugins/test.pl
@@ -0,0 +1,58 @@
+#!/usr/bin/perl -w
+
+use Gaim;
+
+%PLUGIN_INFO = (
+ perl_api_version => 2,
+ name => 'Test Perl Plugin',
+ version => '1.0',
+ summary => 'Provides as a test base for the perl plugin.',
+ description => 'Provides as a test base for the perl plugin.',
+ author => 'Christian Hammond <chipx86@gnupdate.org>',
+ url => 'http://gaim.sf.net/',
+
+ load => "plugin_load",
+ unload => "plugin_unload"
+);
+
+sub account_away_cb {
+ Gaim::debug_info("perl test plugin", "In account_away_cb\n");
+
+ my ($account, $state, $message, $data) = @_;
+
+ Gaim::debug_info("perl test plugin", "Account " .
+ $account->get_username() . " went away.\n");
+ Gaim::debug_info("perl test plugin", $data . "\n");
+}
+
+sub plugin_init {
+ return %PLUGIN_INFO;
+}
+
+sub plugin_load {
+ Gaim::debug_info("perl test plugin", "plugin_load\n");
+ my $plugin = shift;
+
+ Gaim::debug_info("perl test plugin", "Listing accounts.\n");
+ foreach $account (Gaim::accounts()) {
+ Gaim::debug_info("perl test plugin", $account->get_username() . "\n");
+ }
+
+ Gaim::debug_info("perl test plugin", "Listing buddy list.\n");
+ foreach $group (Gaim::BuddyList::groups()) {
+ Gaim::debug_info("perl test plugin",
+ $group->get_name() . ":\n");
+
+ foreach $buddy ($group->buddies()) {
+ Gaim::debug_info("perl test plugin",
+ " " . $buddy->get_name() . "\n");
+ }
+ }
+
+ Gaim::signal_connect(Gaim::Accounts::handle, "account-away",
+ $plugin, \&account_away_cb, "test");
+}
+
+sub plugin_unload {
+ my $plugin = shift;
+}