# HG changeset patch # User Ethan Blanton # Date 1166245195 0 # Node ID b63ebf84c42b33508f0b8159f48f29674ceae045 # Parent d10dda2777a9dc789170ea0c28849290ac0b4ae9 This is a hand-crafted commit to migrate across subversion revisions 16854:16861, due to some vagaries of the way the original renames were done. Witness that monotone can do in one revision what svn had to spread across several. diff -r d10dda2777a9 -r b63ebf84c42b Makefile.am --- a/Makefile.am Sat Aug 19 00:24:14 2006 +0000 +++ b/Makefile.am Sat Dec 16 04:59:55 2006 +0000 @@ -48,7 +48,15 @@ apps_DATA = $(apps_in_files:.desktop.in=.desktop) @INTLTOOL_DESKTOP_RULE@ -SUBDIRS = doc m4macros pixmaps plugins po sounds src +if ENABLE_GTK +GTK_DIR=gtk +endif + +if ENABLE_GNT +GNT_DIR=console +endif + +SUBDIRS = core doc $(GNT_DIR) $(GTK_DIR) m4macros po docs: Doxyfile if HAVE_DOXYGEN diff -r d10dda2777a9 -r b63ebf84c42b configure.ac --- a/configure.ac Sat Aug 19 00:24:14 2006 +0000 +++ b/configure.ac Sat Dec 16 04:59:55 2006 +0000 @@ -527,6 +527,8 @@ AC_ARG_WITH(tkconfig, [ --with-tkconfig=DIR directory containing tkConfig.sh]) AC_ARG_ENABLE(gtkspell, [ --disable-gtkspell compile without GtkSpell automatic spell checking],,enable_gtkspell=yes) AC_ARG_ENABLE(debug, [ --enable-debug compile with debugging support],,enable_debug=no) +AC_ARG_ENABLE(gtkgaim, [ --disable-gtkgaim compile without GtkGaim client],,enable_gtk=yes) +AC_ARG_ENABLE(gntgaim, [ --disable-gntgaim compile without GntGaim console client],,enable_gnt=yes) AC_ARG_ENABLE(fatal-asserts, [ --enable-fatal-asserts make assertions fatal (useful for debugging)],,enable_fatal_asserts=no) dnl We know Gaim won't compile with deprecated APIs disabled. dnl We have no desire to support two different versions of the @@ -633,18 +635,36 @@ fi AC_SUBST(CFLAGS) -AM_PATH_GLIB_2_0(2.0.0,,AC_MSG_ERROR([ +PKG_CHECK_MODULES(GLIB, [glib-2.0 >= 2.0.0 gobject-2.0 gmodule-2.0 gthread-2.0], + [ + AC_SUBST(GLIB_CFLAGS) + AC_SUBST(GLIB_LIBS) + ], + [ + AC_MSG_ERROR([ *** GLib 2.0 is required to build Gaim; please make sure you have the GLib *** development headers installed. The latest version of GLib is -*** always available at http://www.gtk.org/.]),gthread) -AM_PATH_GTK_2_0(2.0.0,,AC_MSG_ERROR([ -*** GTK+ 2.0 is required to build Gaim; please make sure you have the GTK+ -*** development headers installed. The latest version of GTK+ is -*** always available at http://www.gtk.org/.])) +*** always available at http://www.gtk.org/.]) + ]) AC_PATH_PROG(gaimpath, gaim) -AC_SUBST(GTK_CFLAGS) -AC_SUBST(GLIB_CFLAGS) + +if test "$enable_gtk" = yes ; then + PKG_CHECK_MODULES(GTK, [gtk+-2.0 >= 2.0.0], + [ + AC_SUBST(GTK_CFLAGS) + AC_SUBST(GTK_LIBS) + ], + [ + AC_MSG_ERROR([ +*** GTK+ 2.0 is required to build Gaim. please make sure you have the GTK+ +*** development headers installed. The latest version of GTK+ is +*** always available at http://www.gtk.org/. +*** +*** If you wish to build just gntgaim or libgaim, +*** configure with --disable-gtkgaim]) + ]) +fi AC_PATH_XTRA # We can't assume that $x_libraries will be set, because autoconf does not @@ -784,6 +804,33 @@ AM_CONDITIONAL(ENABLE_DBUS, test "x$enable_dbus" = "xyes") dnl ####################################################################### +dnl # GNT Gaim +dnl ####################################################################### +AM_CONDITIONAL(ENABLE_GNT, test "x$enable_gnt" = "xyes") + +dnl ####################################################################### +dnl # Look for startup-notification, evolution integration, X-libraries, +dnl # XScreenSaver, X session management, GtkSpell only if GTK+ was found +dnl ####################################################################### + +if test "x$enable_gtk" = "xyes"; then + +AC_PATH_XTRA +# We can't assume that $x_libraries will be set, because autoconf does not +# set it in the case when the X libraries are in a standard place. +# Ditto for $x_includes +if test X"$x_libraries" = X"" || test X"$x_libraries" = XNONE; then + x_libpath_add= +else + x_libpath_add="-L$x_libraries" +fi +if test X"$x_includes" = X"" || test X"$x_includes" = XNONE; then + x_incpath_add= +else + x_incpath_add="-I$x_includes" +fi + +dnl ####################################################################### dnl # Check for startup notification dnl ####################################################################### AC_ARG_ENABLE(startup-notification, [AC_HELP_STRING([--disable-startup-notification], [compile without startup notification support])], , enable_startup_notification=yes) @@ -803,7 +850,6 @@ AC_SUBST(STARTUP_NOTIFICATION_LIBS) fi - dnl ####################################################################### dnl # Check for stuff needed by the evolution integration plugin. dnl ####################################################################### @@ -834,8 +880,6 @@ AC_SUBST(EVOLUTION_ADDRESSBOOK_LIBS) fi -AM_CONDITIONAL(BUILD_GEVOLUTION, test "x$build_gevo" = "xyes") - dnl ####################################################################### dnl # Check for XScreenSaver dnl ####################################################################### @@ -889,7 +933,6 @@ AC_DEFINE(USE_SM, 1, [Define if we're using X Session Management.]) fi - AC_DEFUN([GC_TM_GMTOFF], [AC_REQUIRE([AC_STRUCT_TM])dnl AC_CACHE_CHECK([for tm_gmtoff in struct tm], ac_cv_struct_tm_gmtoff, @@ -901,6 +944,30 @@ fi ]) +dnl Thanks, Evan. +if test "$enable_gtkspell" = yes ; then + PKG_CHECK_MODULES(GTKSPELL, gtkspell-2.0 >= 2.0.2, [], [ + AC_MSG_RESULT(no) + enable_gtkspell=no + ]) + if test "$enable_gtkspell" = "yes" ; then + AC_SUBST(GTKSPELL_CFLAGS) + AC_SUBST(GTKSPELL_LIBS) + AC_DEFINE(USE_GTKSPELL,,[do we have gtkspell?]) + fi +fi +else # GTK + enable_gevolution=no + enable_sm=no + enable_xss=no + enable_startup_notification=no + enable_gtkspell=no +fi # GTK + +AM_CONDITIONAL(ENABLE_GTK, test "x$enable_gtk" = "xyes") + +AM_CONDITIONAL(BUILD_GEVOLUTION, test "x$build_gevo" = "xyes") + GC_TM_GMTOFF dnl ####################################################################### @@ -943,6 +1010,7 @@ AC_SUBST(MONO_LIBS) AM_CONDITIONAL(USE_MONO, test x"$enable_mono" = x"yes") +if test "x$enable_gtk" = "xyes"; then # This is for now, since perl still requires GTK+ dnl ####################################################################### dnl # Check for Perl support dnl ####################################################################### @@ -950,6 +1018,10 @@ enable_perl=no fi +else # GTK + enable_perl=no +fi # GTK + if test "$enable_perl" = yes ; then AC_PATH_PROG(perlpath, perl) AC_MSG_CHECKING(for Perl compile flags) @@ -1566,19 +1638,6 @@ AM_CONDITIONAL(USE_TK, false) fi -dnl Thanks, Evan. -if test "$enable_gtkspell" = yes ; then - PKG_CHECK_MODULES(GTKSPELL, gtkspell-2.0 >= 2.0.2, [], [ - AC_MSG_RESULT(no) - enable_gtkspell=no - ]) - if test "$enable_gtkspell" = "yes" ; then - AC_SUBST(GTKSPELL_CFLAGS) - AC_SUBST(GTKSPELL_LIBS) - AC_DEFINE(USE_GTKSPELL,,[do we have gtkspell?]) - fi -fi - if test "$ac_cv_cygwin" = yes ; then LDADD="$LDADD -static" AC_DEFINE(DEBUG, 1, [Define if debugging is enabled.]) @@ -1748,43 +1807,47 @@ doc/gaim.1 doc/gntgaim.1 m4macros/Makefile - pixmaps/Makefile - pixmaps/smileys/Makefile - pixmaps/smileys/default/Makefile - pixmaps/smileys/none/Makefile - pixmaps/status/Makefile - pixmaps/status/default/Makefile - plugins/Makefile - plugins/docklet/Makefile - plugins/gevolution/Makefile - plugins/gestures/Makefile - plugins/mono/Makefile - plugins/mono/api/Makefile - plugins/mono/loader/Makefile - plugins/musicmessaging/Makefile - plugins/perl/Makefile - plugins/perl/common/Makefile.PL - plugins/ssl/Makefile - plugins/tcl/Makefile - plugins/ticker/Makefile + gtk/Makefile + gtk/pixmaps/Makefile + gtk/pixmaps/smileys/Makefile + gtk/pixmaps/smileys/default/Makefile + gtk/pixmaps/smileys/none/Makefile + gtk/pixmaps/status/Makefile + gtk/pixmaps/status/default/Makefile + gtk/plugins/Makefile + gtk/plugins/docklet/Makefile + gtk/plugins/gevolution/Makefile + gtk/plugins/gestures/Makefile + gtk/plugins/musicmessaging/Makefile + gtk/sounds/Makefile + gtk/plugins/ticker/Makefile + core/plugins/Makefile + core/plugins/mono/Makefile + core/plugins/mono/api/Makefile + core/plugins/mono/loader/Makefile + core/plugins/perl/Makefile + core/plugins/perl/common/Makefile.PL + core/plugins/ssl/Makefile + core/plugins/tcl/Makefile + core/Makefile + core/protocols/Makefile + core/protocols/bonjour/Makefile + core/protocols/gg/Makefile + core/protocols/irc/Makefile + core/protocols/jabber/Makefile + core/protocols/msn/Makefile + core/protocols/novell/Makefile + core/protocols/oscar/Makefile + core/protocols/qq/Makefile + core/protocols/sametime/Makefile + core/protocols/silc/Makefile + core/protocols/simple/Makefile + core/protocols/toc/Makefile + core/protocols/yahoo/Makefile + core/protocols/zephyr/Makefile + console/Makefile + console/plugins/Makefile po/Makefile.in - sounds/Makefile - src/Makefile - src/protocols/Makefile - src/protocols/bonjour/Makefile - src/protocols/gg/Makefile - src/protocols/irc/Makefile - src/protocols/jabber/Makefile - src/protocols/msn/Makefile - src/protocols/novell/Makefile - src/protocols/oscar/Makefile - src/protocols/qq/Makefile - src/protocols/sametime/Makefile - src/protocols/silc/Makefile - src/protocols/simple/Makefile - src/protocols/toc/Makefile - src/protocols/yahoo/Makefile - src/protocols/zephyr/Makefile gaim.pc gaim.spec ]) @@ -1797,7 +1860,12 @@ echo Protocols to link statically.. : $STATIC_PRPLS echo Protocols to build dynamically : $DYNAMIC_PRPLS echo -echo UI Library.................... : GTK+ 2.x +if test "x$enable_gtk" = "xyes" ; then +echo GTK UI Library................ : GTK+ 2.x +else +echo GTK UI Library................ : None +fi +echo Build with GNT Console UI..... : $enable_gnt echo SSL Library/Libraries......... : $msg_ssl echo echo Build with GStreamer support.. : $enable_gst @@ -1808,7 +1876,7 @@ echo Build with Tk support......... : $enable_tk echo Build with GtkSpell support... : $enable_gtkspell echo Build with DBUS support....... : $enable_dbus -if test x$enable_dbus = xyes ; then +if test "x$enable_dbus" = "xyes" ; then eval echo DBUS servies directory........ : $DBUS_SERVICES_DIR fi echo Build with Cyrus SASL support. : $enable_cyrus_sasl diff -r d10dda2777a9 -r b63ebf84c42b console/Makefile --- a/console/Makefile Sat Aug 19 00:24:14 2006 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,59 +0,0 @@ -VERSION=gntgaim-0.0.0dev -CC=gcc -CFLAGS=`pkg-config --cflags gaim gobject-2.0 gnt` -g -Wall -DVERSION=\"$(VERSION)\" -DSTANDALONE -LDFLAGS=`pkg-config --libs gaim gobject-2.0 libxml-2.0 gnt` -pg -lgaim - -GG_SOURCES = \ - gntaccount.c \ - gntblist.c \ - gntconn.c \ - gntconv.c \ - gntdebug.c \ - gntnotify.c \ - gntplugin.c \ - gntprefs.c \ - gntrequest.c \ - gntstatus.c \ - gntui.c - -GG_HEADERS = \ - gntaccount.h \ - gntblist.h \ - gntconn.h \ - gntconv.h \ - gntdebug.h \ - gntnotify.h \ - gntprefs.h \ - gntplugin.h \ - gntrequest.h \ - gntstatus.h \ - gntui.h - -GG_OBJECTS = \ - gntaccount.o \ - gntblist.o \ - gntconn.o \ - gntconv.o \ - gntdebug.o \ - gntnotify.o \ - gntplugin.o \ - gntprefs.o \ - gntrequest.o \ - gntstatus.o \ - gntui.o - -all: gntgaim - -gntgaim: gntgaim.o $(GG_OBJECTS) - $(CC) -o gntgaim gntgaim.o $(GG_OBJECTS) $(LDFLAGS) -gntaccount.o: gntaccount.c $(GG_HEADERS) -gntblist.o: gntblist.c $(GG_HEADERS) -gntconv.o: gntconv.c $(GG_HEADERS) -gntgaim.o: gntgaim.c gntgaim.h $(GG_HEADERS) -gntnotify.o: gntnotify.c $(GG_HEADERS) -gntui.o: gntui.c $(GG_HEADERS) - -clean: - rm -f *.o - rm -f gntgaim - diff -r d10dda2777a9 -r b63ebf84c42b console/Makefile.am --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/console/Makefile.am Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,65 @@ +#libgnt currently needs to have autogen run for there to be a Makefile... +# SUBDIRS = libgnt + +bin_PROGRAMS = gntgaim + +gntgaim_SOURCES = \ + gntaccount.c \ + gntblist.c \ + gntconn.c \ + gntconv.c \ + gntdebug.c \ + gntgaim.c \ + gntnotify.c \ + gntplugin.c \ + gntprefs.c \ + gntrequest.c \ + gntstatus.c \ + gntui.c + +gntgaim_headers = \ + gntaccount.h \ + gntblist.h \ + gntconn.h \ + gntconv.h \ + gntdebug.h \ + gntnotify.h \ + gntplugin.c \ + gntprefs.h \ + gntrequest.h \ + gntstatus.h \ + gntui.h + +gntgaimincludedir=$(includedir)/gaim/gnt +gntgaiminclude_HEADERS = \ + $(gntgaim_headers) + +gntgaim_DEPENDENCIES = @LIBOBJS@ $(STATIC_LINK_LIBS) $(MS_LIBS) +gntgaim_LDFLAGS = -export-dynamic +gntgaim_LDADD = \ + @LIBOBJS@ \ + $(DBUS_LIBS) \ + $(GSTREAMER_LIBS) \ + $(STATIC_LINK_LIBS) \ + $(XSS_LIBS) \ + $(SM_LIBS) \ + $(INTLLIBS) \ + $(GLIB_LIBS) \ + $(LIBXML_LIBS) \ + -L./libgnt/ -lgnt \ + -L$(top_srcdir)/core -lgaim + +AM_CPPFLAGS = \ + -DSTANDALONE \ + -DBR_PTHREADS=0 \ + -DDATADIR=\"$(datadir)\" \ + -DLIBDIR=\"$(libdir)/gaim/\" \ + -DLOCALEDIR=\"$(datadir)/locale\" \ + -DSYSCONFDIR=\"$(sysconfdir)\" \ + -I$(top_srcdir)/core/ \ + -I ./libgnt/ \ + $(GSTREAMER_CFLAGS) \ + $(DEBUG_CFLAGS) \ + $(GLIB_CFLAGS) \ + $(DBUS_CFLAGS) \ + $(LIBXML_CFLAGS) diff -r d10dda2777a9 -r b63ebf84c42b console/getopt.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/console/getopt.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,737 @@ +/* Getopt for GNU. + NOTE: getopt is now part of the C library, so if you don't know what + "Keep this file name-space clean" means, talk to roland@gnu.ai.mit.edu + before changing it! + + 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, 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, 675 Mass Ave, Cambridge, MA 02139, USA. */ + +/* NOTE!!! AIX requires this to be the first thing in the file. + Do not put ANYTHING before it! */ +#if !defined (__GNUC__) && defined (_AIX) + #pragma alloca +#endif + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* Alver says we need this for IRIX. */ +#if HAVE_STRING_H +#include "string.h" +#endif + +#ifdef __GNUC__ +#define alloca __builtin_alloca +#else /* not __GNUC__ */ +#if defined (HAVE_ALLOCA_H) || (defined(sparc) && (defined(sun) || (!defined(USG) && !defined(SVR4) && !defined(__svr4__)))) +#include +#else +#ifndef _AIX +char *alloca (); +#endif +#endif /* alloca.h */ +#endif /* not __GNUC__ */ + +#if !__STDC__ && !defined(const) && IN_GCC +#define const +#endif + +/* This tells Alpha OSF/1 not to define a getopt prototype in . */ +#ifndef _NO_PROTO +#define _NO_PROTO +#endif + +#include + +/* Comment out all this code if we are using the GNU C Library, and are not + actually compiling the library itself. This code is part of the GNU C + Library, but also included in many other GNU distributions. Compiling + and linking in this code is a waste when using the GNU C library + (especially if it is a shared library). Rather than having every GNU + program understand `configure --with-gnu-libc' and omit the object files, + it is simpler to just do this in the source for each such file. */ + +#if defined (_LIBC) || !defined (__GNU_LIBRARY__) + + +/* This needs to come after some library #include + to get __GNU_LIBRARY__ defined. */ +#ifdef __GNU_LIBRARY__ +#undef alloca +/* Don't include stdlib.h for non-GNU C libraries because some of them + contain conflicting prototypes for getopt. */ +#include +#else /* Not GNU C library. */ +#define __alloca alloca +#endif /* GNU C library. */ + +/* If GETOPT_COMPAT is defined, `+' as well as `--' can introduce a + long-named option. Because this is not POSIX.2 compliant, it is + being phased out. */ +/* #define GETOPT_COMPAT */ + +/* This version of `getopt' appears to the caller like standard Unix `getopt' + but it behaves differently for the user, since it allows the user + to intersperse the options with the other arguments. + + As `getopt' works, it permutes the elements of ARGV so that, + when it is done, all the options precede everything else. Thus + all application programs are extended to handle flexible argument order. + + Setting the environment variable POSIXLY_CORRECT disables permutation. + Then the behavior is completely standard. + + GNU application programs can use a third alternative mode in which + they can distinguish the relative order of options and other arguments. */ + +#include "getopt.h" + +/* For communication from `getopt' to the caller. + When `getopt' finds an option that takes an argument, + the argument value is returned here. + Also, when `ordering' is RETURN_IN_ORDER, + each non-option ARGV-element is returned here. */ + +char *optarg = 0; + +/* Index in ARGV of the next element to be scanned. + This is used for communication to and from the caller + and for communication between successive calls to `getopt'. + + On entry to `getopt', zero means this is the first call; initialize. + + When `getopt' returns EOF, this is the index of the first of the + non-option elements that the caller should itself scan. + + Otherwise, `optind' communicates from one call to the next + how much of ARGV has been scanned so far. */ + +/* XXX 1003.2 says this must be 1 before any call. */ +int optind = 0; + +/* The next char to be scanned in the option-element + in which the last option character we returned was found. + This allows us to pick up the scan where we left off. + + If this is zero, or a null string, it means resume the scan + by advancing to the next ARGV-element. */ + +static char *nextchar; + +/* Callers store zero here to inhibit the error message + for unrecognized options. */ + +int opterr = 1; + +/* Set to an option character which was unrecognized. + This must be initialized on some systems to avoid linking in the + system's own getopt implementation. */ + +int optopt = '?'; + +/* Describe how to deal with options that follow non-option ARGV-elements. + + If the caller did not specify anything, + the default is REQUIRE_ORDER if the environment variable + POSIXLY_CORRECT is defined, PERMUTE otherwise. + + REQUIRE_ORDER means don't recognize them as options; + stop option processing when the first non-option is seen. + This is what Unix does. + This mode of operation is selected by either setting the environment + variable POSIXLY_CORRECT, or using `+' as the first character + of the list of option characters. + + PERMUTE is the default. We permute the contents of ARGV as we scan, + so that eventually all the non-options are at the end. This allows options + to be given in any order, even with programs that were not written to + expect this. + + RETURN_IN_ORDER is an option available to programs that were written + to expect options and other ARGV-elements in any order and that care about + the ordering of the two. We describe each non-option ARGV-element + as if it were the argument of an option with character code 1. + Using `-' as the first character of the list of option characters + selects this mode of operation. + + The special argument `--' forces an end of option-scanning regardless + of the value of `ordering'. In the case of RETURN_IN_ORDER, only + `--' can cause `getopt' to return EOF with `optind' != ARGC. */ + +static enum +{ + REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER +} ordering; + +#ifdef __GNU_LIBRARY__ +/* We want to avoid inclusion of string.h with non-GNU libraries + because there are many ways it can cause trouble. + On some systems, it contains special magic macros that don't work + in GCC. */ +#include +#define my_index strchr +#define my_bcopy(src, dst, n) memcpy ((dst), (src), (n)) +#else + +/* Avoid depending on library functions or files + whose names are inconsistent. */ + +char *getenv (); + +static char * +my_index (str, chr) + const char *str; + int chr; +{ + while (*str) + { + if (*str == chr) + return (char *) str; + str++; + } + return 0; +} + +static void +my_bcopy (from, to, size) + const char *from; + char *to; + int size; +{ + int i; + for (i = 0; i < size; i++) + to[i] = from[i]; +} +#endif /* GNU C library. */ + +/* Handle permutation of arguments. */ + +/* Describe the part of ARGV that contains non-options that have + been skipped. `first_nonopt' is the index in ARGV of the first of them; + `last_nonopt' is the index after the last of them. */ + +static int first_nonopt; +static int last_nonopt; + +/* Exchange two adjacent subsequences of ARGV. + One subsequence is elements [first_nonopt,last_nonopt) + which contains all the non-options that have been skipped so far. + The other is elements [last_nonopt,optind), which contains all + the options processed since those non-options were skipped. + + `first_nonopt' and `last_nonopt' are relocated so that they describe + the new indices of the non-options in ARGV after they are moved. */ + +static void +exchange (argv) + char **argv; +{ + int nonopts_size = (last_nonopt - first_nonopt) * sizeof (char *); + char **temp = (char **) __alloca (nonopts_size); + + /* Interchange the two blocks of data in ARGV. */ + + my_bcopy ((char *) &argv[first_nonopt], (char *) temp, nonopts_size); + my_bcopy ((char *) &argv[last_nonopt], (char *) &argv[first_nonopt], + (optind - last_nonopt) * sizeof (char *)); + my_bcopy ((char *) temp, + (char *) &argv[first_nonopt + optind - last_nonopt], + nonopts_size); + + /* Update records for the slots the non-options now occupy. */ + + first_nonopt += (optind - last_nonopt); + last_nonopt = optind; +} + +/* Scan elements of ARGV (whose length is ARGC) for option characters + given in OPTSTRING. + + If an element of ARGV starts with '-', and is not exactly "-" or "--", + then it is an option element. The characters of this element + (aside from the initial '-') are option characters. If `getopt' + is called repeatedly, it returns successively each of the option characters + from each of the option elements. + + If `getopt' finds another option character, it returns that character, + updating `optind' and `nextchar' so that the next call to `getopt' can + resume the scan with the following option character or ARGV-element. + + If there are no more option characters, `getopt' returns `EOF'. + Then `optind' is the index in ARGV of the first ARGV-element + that is not an option. (The ARGV-elements have been permuted + so that those that are not options now come last.) + + OPTSTRING is a string containing the legitimate option characters. + If an option character is seen that is not listed in OPTSTRING, + return '?' after printing an error message. If you set `opterr' to + zero, the error message is suppressed but we still return '?'. + + If a char in OPTSTRING is followed by a colon, that means it wants an arg, + so the following text in the same ARGV-element, or the text of the following + ARGV-element, is returned in `optarg'. Two colons mean an option that + wants an optional arg; if there is text in the current ARGV-element, + it is returned in `optarg', otherwise `optarg' is set to zero. + + If OPTSTRING starts with `-' or `+', it requests different methods of + handling the non-option ARGV-elements. + See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above. + + Long-named options begin with `--' instead of `-'. + Their names may be abbreviated as long as the abbreviation is unique + or is an exact match for some defined option. If they have an + argument, it follows the option name in the same ARGV-element, separated + from the option name by a `=', or else the in next ARGV-element. + When `getopt' finds a long-named option, it returns 0 if that option's + `flag' field is nonzero, the value of the option's `val' field + if the `flag' field is zero. + + The elements of ARGV aren't really const, because we permute them. + But we pretend they're const in the prototype to be compatible + with other systems. + + LONGOPTS is a vector of `struct option' terminated by an + element containing a name which is zero. + + LONGIND returns the index in LONGOPT of the long-named option found. + It is only valid when a long-named option has been found by the most + recent call. + + If LONG_ONLY is nonzero, '-' as well as '--' can introduce + long-named options. */ + +int +_getopt_internal (argc, argv, optstring, longopts, longind, long_only) + int argc; + char *const *argv; + const char *optstring; + const struct option *longopts; + int *longind; + int long_only; +{ + int option_index; + + optarg = 0; + + /* Initialize the internal data when the first call is made. + Start processing options with ARGV-element 1 (since ARGV-element 0 + is the program name); the sequence of previously skipped + non-option ARGV-elements is empty. */ + + if (optind == 0) + { + first_nonopt = last_nonopt = optind = 1; + + nextchar = NULL; + + /* Determine how to handle the ordering of options and nonoptions. */ + + if (optstring[0] == '-') + { + ordering = RETURN_IN_ORDER; + ++optstring; + } + else if (optstring[0] == '+') + { + ordering = REQUIRE_ORDER; + ++optstring; + } + else if (getenv ("POSIXLY_CORRECT") != NULL) + ordering = REQUIRE_ORDER; + else + ordering = PERMUTE; + } + + if (nextchar == NULL || *nextchar == '\0') + { + if (ordering == PERMUTE) + { + /* If we have just processed some options following some non-options, + exchange them so that the options come first. */ + + if (first_nonopt != last_nonopt && last_nonopt != optind) + exchange ((char **) argv); + else if (last_nonopt != optind) + first_nonopt = optind; + + /* Now skip any additional non-options + and extend the range of non-options previously skipped. */ + + while (optind < argc + && (argv[optind][0] != '-' || argv[optind][1] == '\0') +#ifdef GETOPT_COMPAT + && (longopts == NULL + || argv[optind][0] != '+' || argv[optind][1] == '\0') +#endif /* GETOPT_COMPAT */ + ) + optind++; + last_nonopt = optind; + } + + /* Special ARGV-element `--' means premature end of options. + Skip it like a null option, + then exchange with previous non-options as if it were an option, + then skip everything else like a non-option. */ + + if (optind != argc && !strcmp (argv[optind], "--")) + { + optind++; + + if (first_nonopt != last_nonopt && last_nonopt != optind) + exchange ((char **) argv); + else if (first_nonopt == last_nonopt) + first_nonopt = optind; + last_nonopt = argc; + + optind = argc; + } + + /* If we have done all the ARGV-elements, stop the scan + and back over any non-options that we skipped and permuted. */ + + if (optind == argc) + { + /* Set the next-arg-index to point at the non-options + that we previously skipped, so the caller will digest them. */ + if (first_nonopt != last_nonopt) + optind = first_nonopt; + return EOF; + } + + /* If we have come to a non-option and did not permute it, + either stop the scan or describe it to the caller and pass it by. */ + + if ((argv[optind][0] != '-' || argv[optind][1] == '\0') +#ifdef GETOPT_COMPAT + && (longopts == NULL + || argv[optind][0] != '+' || argv[optind][1] == '\0') +#endif /* GETOPT_COMPAT */ + ) + { + if (ordering == REQUIRE_ORDER) + return EOF; + optarg = argv[optind++]; + return 1; + } + + /* We have found another option-ARGV-element. + Start decoding its characters. */ + + nextchar = (argv[optind] + 1 + + (longopts != NULL && argv[optind][1] == '-')); + } + + if (longopts != NULL + && ((argv[optind][0] == '-' + && (argv[optind][1] == '-' || long_only)) +#ifdef GETOPT_COMPAT + || argv[optind][0] == '+' +#endif /* GETOPT_COMPAT */ + )) + { + const struct option *p; + char *s = nextchar; + int exact = 0; + int ambig = 0; + const struct option *pfound = NULL; + int indfound; + + while (*s && *s != '=') + s++; + + /* Test all options for either exact match or abbreviated matches. */ + for (p = longopts, option_index = 0; p->name; + p++, option_index++) + if (!strncmp (p->name, nextchar, s - nextchar)) + { + if (s - nextchar == strlen (p->name)) + { + /* Exact match found. */ + pfound = p; + indfound = option_index; + exact = 1; + break; + } + else if (pfound == NULL) + { + /* First nonexact match found. */ + pfound = p; + indfound = option_index; + } + else + /* Second nonexact match found. */ + ambig = 1; + } + + if (ambig && !exact) + { + if (opterr) + fprintf (stderr, "%s: option `%s' is ambiguous\n", + argv[0], argv[optind]); + nextchar += strlen (nextchar); + optind++; + return '?'; + } + + if (pfound != NULL) + { + option_index = indfound; + optind++; + if (*s) + { + /* Don't test has_arg with >, because some C compilers don't + allow it to be used on enums. */ + if (pfound->has_arg) + optarg = s + 1; + else + { + if (opterr) + { + if (argv[optind - 1][1] == '-') + /* --option */ + fprintf (stderr, + "%s: option `--%s' doesn't allow an argument\n", + argv[0], pfound->name); + else + /* +option or -option */ + fprintf (stderr, + "%s: option `%c%s' doesn't allow an argument\n", + argv[0], argv[optind - 1][0], pfound->name); + } + nextchar += strlen (nextchar); + return '?'; + } + } + else if (pfound->has_arg == 1) + { + if (optind < argc) + optarg = argv[optind++]; + else + { + if (opterr) + fprintf (stderr, "%s: option `%s' requires an argument\n", + argv[0], argv[optind - 1]); + nextchar += strlen (nextchar); + return optstring[0] == ':' ? ':' : '?'; + } + } + nextchar += strlen (nextchar); + if (longind != NULL) + *longind = option_index; + if (pfound->flag) + { + *(pfound->flag) = pfound->val; + return 0; + } + return pfound->val; + } + /* Can't find it as a long option. If this is not getopt_long_only, + or the option starts with '--' or is not a valid short + option, then it's an error. + Otherwise interpret it as a short option. */ + if (!long_only || argv[optind][1] == '-' +#ifdef GETOPT_COMPAT + || argv[optind][0] == '+' +#endif /* GETOPT_COMPAT */ + || my_index (optstring, *nextchar) == NULL) + { + if (opterr) + { + if (argv[optind][1] == '-') + /* --option */ + fprintf (stderr, "%s: unrecognized option `--%s'\n", + argv[0], nextchar); + else + /* +option or -option */ + fprintf (stderr, "%s: unrecognized option `%c%s'\n", + argv[0], argv[optind][0], nextchar); + } + nextchar = (char *) ""; + optind++; + return '?'; + } + } + + /* Look at and handle the next option-character. */ + + { + char c = *nextchar++; + char *temp = my_index (optstring, c); + + /* Increment `optind' when we start to process its last character. */ + if (*nextchar == '\0') + ++optind; + + if (temp == NULL || c == ':') + { + if (opterr) + { +#if 0 + if (c < 040 || c >= 0177) + fprintf (stderr, "%s: unrecognized option, character code 0%o\n", + argv[0], c); + else + fprintf (stderr, "%s: unrecognized option `-%c'\n", argv[0], c); +#else + /* 1003.2 specifies the format of this message. */ + fprintf (stderr, "%s: illegal option -- %c\n", argv[0], c); +#endif + } + optopt = c; + return '?'; + } + if (temp[1] == ':') + { + if (temp[2] == ':') + { + /* This is an option that accepts an argument optionally. */ + if (*nextchar != '\0') + { + optarg = nextchar; + optind++; + } + else + optarg = 0; + nextchar = NULL; + } + else + { + /* This is an option that requires an argument. */ + if (*nextchar != '\0') + { + optarg = nextchar; + /* If we end this ARGV-element by taking the rest as an arg, + we must advance to the next element now. */ + optind++; + } + else if (optind == argc) + { + if (opterr) + { +#if 0 + fprintf (stderr, "%s: option `-%c' requires an argument\n", + argv[0], c); +#else + /* 1003.2 specifies the format of this message. */ + fprintf (stderr, "%s: option requires an argument -- %c\n", + argv[0], c); +#endif + } + optopt = c; + if (optstring[0] == ':') + c = ':'; + else + c = '?'; + } + else + /* We already incremented `optind' once; + increment it again when taking next ARGV-elt as argument. */ + optarg = argv[optind++]; + nextchar = NULL; + } + } + return c; + } +} + +int +getopt (argc, argv, optstring) + int argc; + char *const *argv; + const char *optstring; +{ + return _getopt_internal (argc, argv, optstring, + (const struct option *) 0, + (int *) 0, + 0); +} + +#endif /* _LIBC or not __GNU_LIBRARY__. */ + +#ifdef TEST + +/* Compile with -DTEST to make an executable for use in testing + the above definition of `getopt'. */ + +int +main (argc, argv) + int argc; + char **argv; +{ + int c; + int digit_optind = 0; + + while (1) + { + int this_option_optind = optind ? optind : 1; + + c = getopt (argc, argv, "abc:d:0123456789"); + if (c == EOF) + break; + + switch (c) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (digit_optind != 0 && digit_optind != this_option_optind) + printf ("digits occur in two different argv-elements.\n"); + digit_optind = this_option_optind; + printf ("option %c\n", c); + break; + + case 'a': + printf ("option a\n"); + break; + + case 'b': + printf ("option b\n"); + break; + + case 'c': + printf ("option c with value `%s'\n", optarg); + break; + + case '?': + break; + + default: + printf ("?? getopt returned character code 0%o ??\n", c); + } + } + + if (optind < argc) + { + printf ("non-option ARGV-elements: "); + while (optind < argc) + printf ("%s ", argv[optind++]); + printf ("\n"); + } + + exit (0); +} + +#endif /* TEST */ diff -r d10dda2777a9 -r b63ebf84c42b console/getopt.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/console/getopt.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,136 @@ +/* Declarations for getopt. + + NOTE: getopt is now part of the C library, so if you don't know what + "Keep this file name-space clean" means, talk to roland@gnu.ai.mit.edu + before changing it! + + 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, 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, 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#ifndef _GETOPT_H +#define _GETOPT_H 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/* For communication from `getopt' to the caller. + When `getopt' finds an option that takes an argument, + the argument value is returned here. + Also, when `ordering' is RETURN_IN_ORDER, + each non-option ARGV-element is returned here. */ + +extern char *optarg; + +/* Index in ARGV of the next element to be scanned. + This is used for communication to and from the caller + and for communication between successive calls to `getopt'. + + On entry to `getopt', zero means this is the first call; initialize. + + When `getopt' returns EOF, this is the index of the first of the + non-option elements that the caller should itself scan. + + Otherwise, `optind' communicates from one call to the next + how much of ARGV has been scanned so far. */ + +extern int optind; + +/* Callers store zero here to inhibit the error message `getopt' prints + for unrecognized options. */ + +extern int opterr; + +/* Set to an option character which was unrecognized. */ + +extern int optopt; + +/* Describe the long-named options requested by the application. + The LONG_OPTIONS argument to getopt_long or getopt_long_only is a vector + of `struct option' terminated by an element containing a name which is + zero. + + The field `has_arg' is: + no_argument (or 0) if the option does not take an argument, + required_argument (or 1) if the option requires an argument, + optional_argument (or 2) if the option takes an optional argument. + + If the field `flag' is not NULL, it points to a variable that is set + to the value given in the field `val' when the option is found, but + left unchanged if the option is not found. + + To have a long-named option do something other than set an `int' to + a compiled-in constant, such as set a value from `optarg', set the + option's `flag' field to zero and its `val' field to a nonzero + value (the equivalent single-letter option character, if there is + one). For long options that have a zero `flag' field, `getopt' + returns the contents of the `val' field. */ + +struct option +{ +#if __STDC__ + const char *name; +#else + char *name; +#endif + /* has_arg can't be an enum because some compilers complain about + type mismatches in all the code that assumes it is an int. */ + int has_arg; + int *flag; + int val; +}; + +/* Names for the values of the `has_arg' field of `struct option'. */ + +#define no_argument 0 +#define required_argument 1 +#define optional_argument 2 + +#if __STDC__ +#if defined(__GNU_LIBRARY__) +/* Many other libraries have conflicting prototypes for getopt, with + differences in the consts, in stdlib.h. To avoid compilation + errors, only prototype getopt for the GNU C library. */ +extern int getopt (int argc, char *const *argv, const char *shortopts); +#else /* not __GNU_LIBRARY__ */ +extern int getopt (); +#endif /* not __GNU_LIBRARY__ */ +extern int getopt_long (int argc, char *const *argv, const char *shortopts, + const struct option *longopts, int *longind); +extern int getopt_long_only (int argc, char *const *argv, + const char *shortopts, + const struct option *longopts, int *longind); + +/* Internal only. Users should not call this directly. */ +extern int _getopt_internal (int argc, char *const *argv, + const char *shortopts, + const struct option *longopts, int *longind, + int long_only); +#else /* not __STDC__ */ +extern int getopt (); +extern int getopt_long (); +extern int getopt_long_only (); + +extern int _getopt_internal (); +#endif /* not __STDC__ */ + +#ifdef __cplusplus +} +#endif + +#endif /* _GETOPT_H */ diff -r d10dda2777a9 -r b63ebf84c42b console/getopt1.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/console/getopt1.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,177 @@ +/* getopt_long and getopt_long_only entry points for GNU getopt. + 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, 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, 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "getopt.h" + +#if !__STDC__ && !defined(const) && IN_GCC +#define const +#endif + +#include + +/* Comment out all this code if we are using the GNU C Library, and are not + actually compiling the library itself. This code is part of the GNU C + Library, but also included in many other GNU distributions. Compiling + and linking in this code is a waste when using the GNU C library + (especially if it is a shared library). Rather than having every GNU + program understand `configure --with-gnu-libc' and omit the object files, + it is simpler to just do this in the source for each such file. */ + +#if defined (_LIBC) || !defined (__GNU_LIBRARY__) + + +/* This needs to come after some library #include + to get __GNU_LIBRARY__ defined. */ +#ifdef __GNU_LIBRARY__ +#include +#else +char *getenv (); +#endif + +#ifndef NULL +#define NULL 0 +#endif + +int +getopt_long (argc, argv, options, long_options, opt_index) + int argc; + char *const *argv; + const char *options; + const struct option *long_options; + int *opt_index; +{ + return _getopt_internal (argc, argv, options, long_options, opt_index, 0); +} + +/* Like getopt_long, but '-' as well as '--' can indicate a long option. + If an option that starts with '-' (not '--') doesn't match a long option, + but does match a short option, it is parsed as a short option + instead. */ + +int +getopt_long_only (argc, argv, options, long_options, opt_index) + int argc; + char *const *argv; + const char *options; + const struct option *long_options; + int *opt_index; +{ + return _getopt_internal (argc, argv, options, long_options, opt_index, 1); +} + + +#endif /* _LIBC or not __GNU_LIBRARY__. */ + +#ifdef TEST + +#include + +int +main (argc, argv) + int argc; + char **argv; +{ + int c; + int digit_optind = 0; + + while (1) + { + int this_option_optind = optind ? optind : 1; + int option_index = 0; + static struct option long_options[] = + { + {"add", 1, 0, 0}, + {"append", 0, 0, 0}, + {"delete", 1, 0, 0}, + {"verbose", 0, 0, 0}, + {"create", 0, 0, 0}, + {"file", 1, 0, 0}, + {0, 0, 0, 0} + }; + + c = getopt_long (argc, argv, "abc:d:0123456789", + long_options, &option_index); + if (c == EOF) + break; + + switch (c) + { + case 0: + printf ("option %s", long_options[option_index].name); + if (optarg) + printf (" with arg %s", optarg); + printf ("\n"); + break; + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (digit_optind != 0 && digit_optind != this_option_optind) + printf ("digits occur in two different argv-elements.\n"); + digit_optind = this_option_optind; + printf ("option %c\n", c); + break; + + case 'a': + printf ("option a\n"); + break; + + case 'b': + printf ("option b\n"); + break; + + case 'c': + printf ("option c with value `%s'\n", optarg); + break; + + case 'd': + printf ("option d with value `%s'\n", optarg); + break; + + case '?': + break; + + default: + printf ("?? getopt returned character code 0%o ??\n", c); + } + } + + if (optind < argc) + { + printf ("non-option ARGV-elements: "); + while (optind < argc) + printf ("%s ", argv[optind++]); + printf ("\n"); + } + + exit (0); +} + +#endif /* TEST */ diff -r d10dda2777a9 -r b63ebf84c42b console/gntconv.c --- a/console/gntconv.c Sat Aug 19 00:24:14 2006 +0000 +++ b/console/gntconv.c Sat Dec 16 04:59:55 2006 +0000 @@ -20,6 +20,8 @@ #define PREF_ROOT "/gaim/gnt/conversations" +#include "config.h" + GHashTable *ggconvs; typedef struct _GGConv GGConv; diff -r d10dda2777a9 -r b63ebf84c42b console/gntgaim.c --- a/console/gntgaim.c Sat Aug 19 00:24:14 2006 +0000 +++ b/console/gntgaim.c Sat Dec 16 04:59:55 2006 +0000 @@ -23,6 +23,8 @@ #define _GNU_SOURCE #include +#include "config.h" + static void debug_init() { diff -r d10dda2777a9 -r b63ebf84c42b core/Makefile.am --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/Makefile.am Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,274 @@ +EXTRA_DIST = \ + dbus-analyze-functions.py \ + dbus-analyze-types.py \ + gaim-notifications-example \ + gaim-remote \ + gaim-send \ + gaim-send-async \ + Makefile.mingw \ + win_gaim.c \ + win32/IdleTracker/Makefile.mingw \ + win32/IdleTracker/idletrack.c \ + win32/IdleTracker/idletrack.h \ + win32/gaimrc.rc \ + win32/global.mak \ + win32/libc_interface.c \ + win32/libc_interface.h \ + win32/libc_internal.h \ + win32/resource.h \ + win32/untar.c \ + win32/untar.h \ + win32/wgaimerror.h \ + win32/win32dep.c \ + win32/win32dep.h \ + win32/wspell.c \ + win32/wspell.h \ + win32/nsis/gaim-header.bmp \ + win32/nsis/gaim-intro.bmp \ + win32/nsis/gaim-plugin.nsh \ + win32/nsis/langmacros.nsh \ + win32/nsis/translations/albanian.nsh \ + win32/nsis/translations/bulgarian.nsh \ + win32/nsis/translations/catalan.nsh \ + win32/nsis/translations/czech.nsh \ + win32/nsis/translations/danish.nsh \ + win32/nsis/translations/dutch.nsh \ + win32/nsis/translations/english.nsh \ + win32/nsis/translations/finnish.nsh \ + win32/nsis/translations/french.nsh \ + win32/nsis/translations/german.nsh \ + win32/nsis/translations/hebrew.nsh \ + win32/nsis/translations/hungarian.nsh \ + win32/nsis/translations/italian.nsh \ + win32/nsis/translations/japanese.nsh \ + win32/nsis/translations/korean.nsh \ + win32/nsis/translations/kurdish.nsh \ + win32/nsis/translations/lithuanian.nsh \ + win32/nsis/translations/norwegian.nsh \ + win32/nsis/translations/polish.nsh \ + win32/nsis/translations/portuguese.nsh \ + win32/nsis/translations/portuguese-br.nsh \ + win32/nsis/translations/romanian.nsh \ + win32/nsis/translations/russian.nsh \ + win32/nsis/translations/serbian-latin.nsh \ + win32/nsis/translations/simp-chinese.nsh \ + win32/nsis/translations/slovak.nsh \ + win32/nsis/translations/slovenian.nsh \ + win32/nsis/translations/spanish.nsh \ + win32/nsis/translations/swedish.nsh \ + win32/nsis/translations/trad-chinese.nsh \ + win32/nsis/translations/vietnamese.nsh + +SUBDIRS = plugins protocols + +gaim_coresources = \ + account.c \ + accountopt.c \ + blist.c \ + buddyicon.c \ + cipher.c \ + circbuffer.c \ + cmds.c \ + connection.c \ + conversation.c \ + core.c \ + debug.c \ + desktopitem.c \ + eventloop.c \ + ft.c \ + idle.c \ + imgstore.c \ + log.c \ + mime.c \ + network.c \ + ntlm.c \ + notify.c \ + plugin.c \ + pluginpref.c \ + pounce.c \ + prefix.c \ + prefs.c \ + privacy.c \ + proxy.c \ + prpl.c \ + request.c \ + roomlist.c \ + savedstatuses.c \ + server.c \ + signals.c \ + dnssrv.c\ + status.c \ + stringref.c \ + stun.c \ + sound.c \ + sslconn.c \ + upnp.c \ + util.c \ + value.c \ + xmlnode.c \ + whiteboard.c + +gaim_coreheaders = \ + account.h \ + accountopt.h \ + blist.h \ + buddyicon.h \ + cipher.h \ + circbuffer.h \ + cmds.h \ + connection.h \ + conversation.h \ + core.h \ + dbus-maybe.h \ + debug.h \ + desktopitem.h \ + eventloop.h \ + ft.h \ + idle.h \ + imgstore.h \ + log.h \ + mime.h \ + network.h \ + notify.h \ + ntlm.h \ + plugin.h \ + pluginpref.h \ + pounce.h \ + prefs.h \ + privacy.h \ + proxy.h \ + prpl.h \ + request.h \ + roomlist.h \ + savedstatuses.h \ + server.h \ + signals.h \ + dnssrv.h \ + status.h \ + stringref.h \ + stun.h \ + sound.h \ + sslconn.h \ + upnp.h \ + util.h \ + value.h \ + version.h \ + xmlnode.h \ + whiteboard.h + +if ENABLE_DBUS + +CLEANFILES = \ + dbus-bindings.c \ + dbus-client-binding.c \ + dbus-client-binding.h \ + dbus-types.c \ + dbus-types.h \ + gaim-client-bindings.c \ + gaim-client-bindings.h \ + gaim.service + +# gaim dbus server + +dbus_sources = dbus-server.c dbus-useful.c +dbus_headers = dbus-bindings.h dbus-gaim.h dbus-server.h dbus-useful.h dbus-define-api.h + +dbus_exported = dbus-useful.h dbus-define-api.h account.h blist.h buddyicon.h connection.h conversation.h core.h log.h roomlist.h savedstatuses.h status.h server.h + +gaim_build_coreheaders = $(addprefix $(srcdir)/, $(gaim_coreheaders)) +dbus_build_exported = $(addprefix $(srcdir)/, $(dbus_exported)) + +dbus-types.c: dbus-analyze-types.py $(gaim_coreheaders) + cat $(gaim_build_coreheaders) | $(PYTHON) $(srcdir)/dbus-analyze-types.py --pattern=GAIM_DBUS_DEFINE_TYPE\(%s\) > $@ + +dbus-types.h: dbus-analyze-types.py $(dbus_coreheaders) + cat $(gaim_build_coreheaders) | $(PYTHON) $(srcdir)/dbus-analyze-types.py --pattern=GAIM_DBUS_DECLARE_TYPE\(%s\) > $@ + +dbus-bindings.c: dbus-analyze-functions.py $(dbus_exported) + cat $(dbus_build_exported) | $(PYTHON) $(srcdir)/dbus-analyze-functions.py > $@ + +dbus-server.$(OBJEXT): dbus-bindings.c dbus-types.c dbus-types.h +dbus-server.lo: dbus-bindings.c dbus-types.c dbus-types.h +$(libgaim_la_OBJECTS): dbus-types.h + +# libgaim-client + +libgaim_client_lib = libgaim-client.la + +libgaim_client_la_SOURCES = gaim-client.c gaim-client.h + +libgaim_client_la_LIBADD = $(DBUS_LIBS) + +gaim-client-bindings.c: dbus-analyze-functions.py $(dbus_exported) + cat $(dbus_build_exported) | $(PYTHON) $(srcdir)/dbus-analyze-functions.py --client > $@ + +gaim-client-bindings.h: dbus-analyze-types.py dbus-analyze-functions.py $(gaim_coreheaders) $(dbus_exported) + cat $(gaim_build_coreheaders) | $(PYTHON) $(srcdir)/dbus-analyze-types.py --keyword=enum --verbatim > $@ + cat $(dbus_build_exported) | $(PYTHON) $(srcdir)/dbus-analyze-functions.py --client --headers >> $@ + +$(libgaim_client_la_OBJECTS): gaim-client-bindings.h gaim-client-bindings.c + +# gaim-client-example + +gaim_client_example_SOURCES = gaim-client-example.c + +gaim_client_example_DEPENDENCIES = @LIBOBJS@ libgaim-client.la + +gaim_client_example_LDADD = \ + @LIBOBJS@ \ + libgaim-client.la \ + $(GLIB_LIBS) \ + $(DBUS_LIBS) + +bin_PROGRAMS = gaim-client-example + +gaim-client-example.$(OBJEXT): gaim-client-bindings.h + +# scripts + +bin_SCRIPTS = gaim-remote gaim-send gaim-send-async + +exampledir = $(datadir)/doc/@PACKAGE@/examples +example_DATA = gaim-notifications-example + +endif + +lib_LTLIBRARIES = libgaim.la $(libgaim_client_lib) + +libgaim_la_SOURCES = \ + $(gaim_coresources) \ + $(dbus_sources) + +noinst_HEADERS= \ + internal.h \ + prefix.h + +libgaimincludedir=$(includedir)/gaim +libgaiminclude_HEADERS = \ + $(gaim_coreheaders) \ + $(dbus_headers) + +libgaim_la_DEPENDENCIES = @LIBOBJS@ $(STATIC_LINK_LIBS) $(MS_LIBS) +libgaim_la_LDFLAGS = -export-dynamic +libgaim_la_LIBADD = \ + @LIBOBJS@ \ + $(DBUS_LIBS) \ + $(GLIB_LIBS) \ + $(GMODULE_LIBS) \ + $(GOBJECT_LIBS) \ + $(GSTREAMER_LIBS) \ + $(STATIC_LINK_LIBS) \ + $(INTLLIBS) \ + -lm + +AM_CPPFLAGS = \ + -DBR_PTHREADS=0 \ + -DDATADIR=\"$(datadir)\" \ + -DLIBDIR=\"$(libdir)/gaim/\" \ + -DLOCALEDIR=\"$(datadir)/locale\" \ + -DSYSCONFDIR=\"$(sysconfdir)\" \ + -I$(top_srcdir)/plugins \ + $(GSTREAMER_CFLAGS) \ + $(DEBUG_CFLAGS) \ + $(DBUS_CFLAGS) \ + $(LIBXML_CFLAGS) diff -r d10dda2777a9 -r b63ebf84c42b core/account.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/account.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,2364 @@ +/** + * @file account.c Account API + * @ingroup core + * + * 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 "account.h" +#include "core.h" +#include "dbus-maybe.h" +#include "debug.h" +#include "notify.h" +#include "pounce.h" +#include "prefs.h" +#include "privacy.h" +#include "prpl.h" +#include "request.h" +#include "server.h" +#include "signals.h" +#include "status.h" +#include "util.h" +#include "xmlnode.h" + +/* TODO: Should use GaimValue instead of this? What about "ui"? */ +typedef struct +{ + GaimPrefType type; + + char *ui; + + union + { + int integer; + char *string; + gboolean bool; + + } value; + +} GaimAccountSetting; + + +static GaimAccountUiOps *account_ui_ops = NULL; + +static GList *accounts = NULL; +static guint save_timer = 0; +static gboolean accounts_loaded = FALSE; + + +/********************************************************************* + * Writing to disk * + *********************************************************************/ + +static void +setting_to_xmlnode(gpointer key, gpointer value, gpointer user_data) +{ + const char *name; + GaimAccountSetting *setting; + xmlnode *node, *child; + char buf[20]; + + name = (const char *)key; + setting = (GaimAccountSetting *)value; + node = (xmlnode *)user_data; + + child = xmlnode_new_child(node, "setting"); + xmlnode_set_attrib(child, "name", name); + + if (setting->type == GAIM_PREF_INT) { + xmlnode_set_attrib(child, "type", "int"); + snprintf(buf, sizeof(buf), "%d", setting->value.integer); + xmlnode_insert_data(child, buf, -1); + } + else if (setting->type == GAIM_PREF_STRING && setting->value.string != NULL) { + xmlnode_set_attrib(child, "type", "string"); + xmlnode_insert_data(child, setting->value.string, -1); + } + else if (setting->type == GAIM_PREF_BOOLEAN) { + xmlnode_set_attrib(child, "type", "bool"); + snprintf(buf, sizeof(buf), "%d", setting->value.bool); + xmlnode_insert_data(child, buf, -1); + } +} + +static void +ui_setting_to_xmlnode(gpointer key, gpointer value, gpointer user_data) +{ + const char *ui; + GHashTable *table; + xmlnode *node, *child; + + ui = (const char *)key; + table = (GHashTable *)value; + node = (xmlnode *)user_data; + + if (g_hash_table_size(table) > 0) + { + child = xmlnode_new_child(node, "settings"); + xmlnode_set_attrib(child, "ui", ui); + g_hash_table_foreach(table, setting_to_xmlnode, child); + } +} + +static xmlnode * +status_attr_to_xmlnode(const GaimStatus *status, const GaimStatusType *type, const GaimStatusAttr *attr) +{ + xmlnode *node; + const char *id; + char *value = NULL; + GaimStatusAttr *default_attr; + GaimValue *default_value; + GaimType attr_type; + GaimValue *attr_value; + + id = gaim_status_attr_get_id(attr); + g_return_val_if_fail(id, NULL); + + attr_value = gaim_status_get_attr_value(status, id); + g_return_val_if_fail(attr_value, NULL); + attr_type = gaim_value_get_type(attr_value); + + /* + * If attr_value is a different type than it should be + * then don't write it to the file. + */ + default_attr = gaim_status_type_get_attr(type, id); + default_value = gaim_status_attr_get_value(default_attr); + if (attr_type != gaim_value_get_type(default_value)) + return NULL; + + /* + * If attr_value is the same as the default for this status + * then there is no need to write it to the file. + */ + if (attr_type == GAIM_TYPE_STRING) + { + const char *string_value = gaim_value_get_string(attr_value); + const char *default_string_value = gaim_value_get_string(default_value); + if (((string_value == NULL) && (default_string_value == NULL)) || + ((string_value != NULL) && (default_string_value != NULL) && + !strcmp(string_value, default_string_value))) + return NULL; + value = g_strdup(gaim_value_get_string(attr_value)); + } + else if (attr_type == GAIM_TYPE_INT) + { + int int_value = gaim_value_get_int(attr_value); + if (int_value == gaim_value_get_int(default_value)) + return NULL; + value = g_strdup_printf("%d", int_value); + } + else if (attr_type == GAIM_TYPE_BOOLEAN) + { + gboolean boolean_value = gaim_value_get_boolean(attr_value); + if (boolean_value == gaim_value_get_boolean(default_value)) + return NULL; + value = g_strdup(boolean_value ? + "true" : "false"); + } + else + { + return NULL; + } + + g_return_val_if_fail(value, NULL); + + node = xmlnode_new("attribute"); + + xmlnode_set_attrib(node, "id", id); + xmlnode_set_attrib(node, "value", value); + + g_free(value); + + return node; +} + +static xmlnode * +status_attrs_to_xmlnode(const GaimStatus *status) +{ + GaimStatusType *type = gaim_status_get_type(status); + xmlnode *node, *child; + const GList *attrs, *attr; + + node = xmlnode_new("attributes"); + + attrs = gaim_status_type_get_attrs(type); + for (attr = attrs; attr != NULL; attr = attr->next) + { + child = status_attr_to_xmlnode(status, type, (const GaimStatusAttr *)attr->data); + if (child) + xmlnode_insert_child(node, child); + } + + return node; +} + +static xmlnode * +status_to_xmlnode(const GaimStatus *status) +{ + xmlnode *node, *child; + + node = xmlnode_new("status"); + xmlnode_set_attrib(node, "type", gaim_status_get_id(status)); + if (gaim_status_get_name(status) != NULL) + xmlnode_set_attrib(node, "name", gaim_status_get_name(status)); + xmlnode_set_attrib(node, "active", gaim_status_is_active(status) ? "true" : "false"); + + child = status_attrs_to_xmlnode(status); + xmlnode_insert_child(node, child); + + return node; +} + +static xmlnode * +statuses_to_xmlnode(const GaimPresence *presence) +{ + xmlnode *node, *child; + const GList *statuses, *status; + + node = xmlnode_new("statuses"); + + statuses = gaim_presence_get_statuses(presence); + for (status = statuses; status != NULL; status = status->next) + { + child = status_to_xmlnode((GaimStatus *)status->data); + xmlnode_insert_child(node, child); + } + + return node; +} + +static xmlnode * +proxy_settings_to_xmlnode(GaimProxyInfo *proxy_info) +{ + xmlnode *node, *child; + GaimProxyType proxy_type; + const char *value; + int int_value; + char buf[20]; + + proxy_type = gaim_proxy_info_get_type(proxy_info); + + node = xmlnode_new("proxy"); + + child = xmlnode_new_child(node, "type"); + xmlnode_insert_data(child, + (proxy_type == GAIM_PROXY_USE_GLOBAL ? "global" : + proxy_type == GAIM_PROXY_NONE ? "none" : + proxy_type == GAIM_PROXY_HTTP ? "http" : + proxy_type == GAIM_PROXY_SOCKS4 ? "socks4" : + proxy_type == GAIM_PROXY_SOCKS5 ? "socks5" : + proxy_type == GAIM_PROXY_USE_ENVVAR ? "envvar" : "unknown"), -1); + + if (proxy_type != GAIM_PROXY_USE_GLOBAL && + proxy_type != GAIM_PROXY_NONE && + proxy_type != GAIM_PROXY_USE_ENVVAR) + { + if ((value = gaim_proxy_info_get_host(proxy_info)) != NULL) + { + child = xmlnode_new_child(node, "host"); + xmlnode_insert_data(child, value, -1); + } + + if ((int_value = gaim_proxy_info_get_port(proxy_info)) != 0) + { + snprintf(buf, sizeof(buf), "%d", int_value); + child = xmlnode_new_child(node, "port"); + xmlnode_insert_data(child, buf, -1); + } + + if ((value = gaim_proxy_info_get_username(proxy_info)) != NULL) + { + child = xmlnode_new_child(node, "username"); + xmlnode_insert_data(child, value, -1); + } + + if ((value = gaim_proxy_info_get_password(proxy_info)) != NULL) + { + child = xmlnode_new_child(node, "password"); + xmlnode_insert_data(child, value, -1); + } + } + + return node; +} + +static xmlnode * +account_to_xmlnode(GaimAccount *account) +{ + xmlnode *node, *child; + const char *tmp; + GaimPresence *presence; + GaimProxyInfo *proxy_info; + + node = xmlnode_new("account"); + + child = xmlnode_new_child(node, "protocol"); + xmlnode_insert_data(child, gaim_account_get_protocol_id(account), -1); + + child = xmlnode_new_child(node, "name"); + xmlnode_insert_data(child, gaim_account_get_username(account), -1); + + if (gaim_account_get_remember_password(account) && + ((tmp = gaim_account_get_password(account)) != NULL)) + { + child = xmlnode_new_child(node, "password"); + xmlnode_insert_data(child, tmp, -1); + } + + if ((tmp = gaim_account_get_alias(account)) != NULL) + { + child = xmlnode_new_child(node, "alias"); + xmlnode_insert_data(child, tmp, -1); + } + + if ((presence = gaim_account_get_presence(account)) != NULL) + { + child = statuses_to_xmlnode(presence); + xmlnode_insert_child(node, child); + } + + if ((tmp = gaim_account_get_user_info(account)) != NULL) + { + /* TODO: Do we need to call gaim_str_strip_char(tmp, '\r') here? */ + child = xmlnode_new_child(node, "userinfo"); + xmlnode_insert_data(child, tmp, -1); + } + + if ((tmp = gaim_account_get_buddy_icon(account)) != NULL) + { + child = xmlnode_new_child(node, "buddyicon"); + xmlnode_insert_data(child, tmp, -1); + } + + if (g_hash_table_size(account->settings) > 0) + { + child = xmlnode_new_child(node, "settings"); + g_hash_table_foreach(account->settings, setting_to_xmlnode, child); + } + + if (g_hash_table_size(account->ui_settings) > 0) + { + g_hash_table_foreach(account->ui_settings, ui_setting_to_xmlnode, node); + } + + if ((proxy_info = gaim_account_get_proxy_info(account)) != NULL) + { + child = proxy_settings_to_xmlnode(proxy_info); + xmlnode_insert_child(node, child); + } + + return node; +} + +static xmlnode * +accounts_to_xmlnode(void) +{ + xmlnode *node, *child; + GList *cur; + + node = xmlnode_new("account"); + xmlnode_set_attrib(node, "version", "1.0"); + + for (cur = gaim_accounts_get_all(); cur != NULL; cur = cur->next) + { + child = account_to_xmlnode(cur->data); + xmlnode_insert_child(node, child); + } + + return node; +} + +static void +sync_accounts(void) +{ + xmlnode *node; + char *data; + + if (!accounts_loaded) + { + gaim_debug_error("account", "Attempted to save accounts before " + "they were read!\n"); + return; + } + + node = accounts_to_xmlnode(); + data = xmlnode_to_formatted_str(node, NULL); + gaim_util_write_data_to_file("accounts.xml", data, -1); + g_free(data); + xmlnode_free(node); +} + +static gboolean +save_cb(gpointer data) +{ + sync_accounts(); + save_timer = 0; + return FALSE; +} + +static void +schedule_accounts_save() +{ + if (save_timer == 0) + save_timer = gaim_timeout_add(5000, save_cb, NULL); +} + + +/********************************************************************* + * Reading from disk * + *********************************************************************/ + +static void +parse_settings(xmlnode *node, GaimAccount *account) +{ + const char *ui; + xmlnode *child; + + /* Get the UI string, if these are UI settings */ + ui = xmlnode_get_attrib(node, "ui"); + + /* Read settings, one by one */ + for (child = xmlnode_get_child(node, "setting"); child != NULL; + child = xmlnode_get_next_twin(child)) + { + const char *name, *str_type; + GaimPrefType type; + char *data; + + name = xmlnode_get_attrib(child, "name"); + if (name == NULL) + /* Ignore this setting */ + continue; + + str_type = xmlnode_get_attrib(child, "type"); + if (str_type == NULL) + /* Ignore this setting */ + continue; + + if (!strcmp(str_type, "string")) + type = GAIM_PREF_STRING; + else if (!strcmp(str_type, "int")) + type = GAIM_PREF_INT; + else if (!strcmp(str_type, "bool")) + type = GAIM_PREF_BOOLEAN; + else + /* Ignore this setting */ + continue; + + data = xmlnode_get_data(child); + if (data == NULL) + /* Ignore this setting */ + continue; + + if (ui == NULL) + { + if (type == GAIM_PREF_STRING) + gaim_account_set_string(account, name, data); + else if (type == GAIM_PREF_INT) + gaim_account_set_int(account, name, atoi(data)); + else if (type == GAIM_PREF_BOOLEAN) + gaim_account_set_bool(account, name, + (*data == '0' ? FALSE : TRUE)); + } else { + if (type == GAIM_PREF_STRING) + gaim_account_set_ui_string(account, ui, name, data); + else if (type == GAIM_PREF_INT) + gaim_account_set_ui_int(account, ui, name, atoi(data)); + else if (type == GAIM_PREF_BOOLEAN) + gaim_account_set_ui_bool(account, ui, name, + (*data == '0' ? FALSE : TRUE)); + } + + g_free(data); + } +} + +static GList * +parse_status_attrs(xmlnode *node, GaimStatus *status) +{ + GList *list = NULL; + xmlnode *child; + GaimValue *attr_value; + + for (child = xmlnode_get_child(node, "attribute"); child != NULL; + child = xmlnode_get_next_twin(child)) + { + const char *id = xmlnode_get_attrib(child, "id"); + const char *value = xmlnode_get_attrib(child, "value"); + + if (!id || !*id || !value || !*value) + continue; + + attr_value = gaim_status_get_attr_value(status, id); + if (!attr_value) + continue; + + list = g_list_append(list, (char *)id); + + switch (gaim_value_get_type(attr_value)) + { + case GAIM_TYPE_STRING: + list = g_list_append(list, (char *)value); + break; + case GAIM_TYPE_INT: + case GAIM_TYPE_BOOLEAN: + { + int v; + if (sscanf(value, "%d", &v) == 1) + list = g_list_append(list, GINT_TO_POINTER(v)); + else + list = g_list_remove(list, id); + break; + } + default: + break; + } + } + + return list; +} + +static void +parse_status(xmlnode *node, GaimAccount *account) +{ + gboolean active = FALSE; + const char *data; + const char *type; + xmlnode *child; + GList *attrs = NULL; + + /* Get the active/inactive state */ + data = xmlnode_get_attrib(node, "active"); + if (data == NULL) + return; + if (strcasecmp(data, "true") == 0) + active = TRUE; + else if (strcasecmp(data, "false") == 0) + active = FALSE; + else + return; + + /* Get the type of the status */ + type = xmlnode_get_attrib(node, "type"); + if (type == NULL) + return; + + /* Read attributes into a GList */ + child = xmlnode_get_child(node, "attributes"); + if (child != NULL) + { + attrs = parse_status_attrs(child, + gaim_account_get_status(account, type)); + } + + gaim_account_set_status_list(account, type, active, attrs); + + g_list_free(attrs); +} + +static void +parse_statuses(xmlnode *node, GaimAccount *account) +{ + xmlnode *child; + + for (child = xmlnode_get_child(node, "status"); child != NULL; + child = xmlnode_get_next_twin(child)) + { + parse_status(child, account); + } +} + +static void +parse_proxy_info(xmlnode *node, GaimAccount *account) +{ + GaimProxyInfo *proxy_info; + xmlnode *child; + char *data; + + proxy_info = gaim_proxy_info_new(); + + /* Use the global proxy settings, by default */ + gaim_proxy_info_set_type(proxy_info, GAIM_PROXY_USE_GLOBAL); + + /* Read proxy type */ + child = xmlnode_get_child(node, "type"); + if ((child != NULL) && ((data = xmlnode_get_data(child)) != NULL)) + { + if (!strcmp(data, "global")) + gaim_proxy_info_set_type(proxy_info, GAIM_PROXY_USE_GLOBAL); + else if (!strcmp(data, "none")) + gaim_proxy_info_set_type(proxy_info, GAIM_PROXY_NONE); + else if (!strcmp(data, "http")) + gaim_proxy_info_set_type(proxy_info, GAIM_PROXY_HTTP); + else if (!strcmp(data, "socks4")) + gaim_proxy_info_set_type(proxy_info, GAIM_PROXY_SOCKS4); + else if (!strcmp(data, "socks5")) + gaim_proxy_info_set_type(proxy_info, GAIM_PROXY_SOCKS5); + else if (!strcmp(data, "envvar")) + gaim_proxy_info_set_type(proxy_info, GAIM_PROXY_USE_ENVVAR); + else + { + gaim_debug_error("account", "Invalid proxy type found when " + "loading account information for %s\n", + gaim_account_get_username(account)); + } + g_free(data); + } + + /* Read proxy host */ + child = xmlnode_get_child(node, "host"); + if ((child != NULL) && ((data = xmlnode_get_data(child)) != NULL)) + { + gaim_proxy_info_set_host(proxy_info, data); + g_free(data); + } + + /* Read proxy port */ + child = xmlnode_get_child(node, "port"); + if ((child != NULL) && ((data = xmlnode_get_data(child)) != NULL)) + { + gaim_proxy_info_set_port(proxy_info, atoi(data)); + g_free(data); + } + + /* Read proxy username */ + child = xmlnode_get_child(node, "username"); + if ((child != NULL) && ((data = xmlnode_get_data(child)) != NULL)) + { + gaim_proxy_info_set_username(proxy_info, data); + g_free(data); + } + + /* Read proxy password */ + child = xmlnode_get_child(node, "password"); + if ((child != NULL) && ((data = xmlnode_get_data(child)) != NULL)) + { + gaim_proxy_info_set_password(proxy_info, data); + g_free(data); + } + + /* If there are no values set then proxy_infourn NULL */ + if ((gaim_proxy_info_get_type(proxy_info) == GAIM_PROXY_USE_GLOBAL) && + (gaim_proxy_info_get_host(proxy_info) == NULL) && + (gaim_proxy_info_get_port(proxy_info) == 0) && + (gaim_proxy_info_get_username(proxy_info) == NULL) && + (gaim_proxy_info_get_password(proxy_info) == NULL)) + { + gaim_proxy_info_destroy(proxy_info); + return; + } + + gaim_account_set_proxy_info(account, proxy_info); +} + +static GaimAccount * +parse_account(xmlnode *node) +{ + GaimAccount *ret; + xmlnode *child; + char *protocol_id = NULL; + char *name = NULL; + char *data; + + child = xmlnode_get_child(node, "protocol"); + if (child != NULL) + protocol_id = xmlnode_get_data(child); + + child = xmlnode_get_child(node, "name"); + if (child != NULL) + name = xmlnode_get_data(child); + if (name == NULL) + { + /* Do we really need to do this? */ + child = xmlnode_get_child(node, "username"); + if (child != NULL) + name = xmlnode_get_data(child); + } + + if ((protocol_id == NULL) || (name == NULL)) + { + g_free(protocol_id); + g_free(name); + return NULL; + } + + ret = gaim_account_new(name, protocol_id); + g_free(name); + g_free(protocol_id); + + /* Read the password */ + child = xmlnode_get_child(node, "password"); + if ((child != NULL) && ((data = xmlnode_get_data(child)) != NULL)) + { + gaim_account_set_remember_password(ret, TRUE); + gaim_account_set_password(ret, data); + g_free(data); + } + + /* Read the alias */ + child = xmlnode_get_child(node, "alias"); + if ((child != NULL) && ((data = xmlnode_get_data(child)) != NULL)) + { + if (*data != '\0') + gaim_account_set_alias(ret, data); + g_free(data); + } + + /* Read the statuses */ + child = xmlnode_get_child(node, "statuses"); + if (child != NULL) + { + parse_statuses(child, ret); + } + + /* Read the userinfo */ + child = xmlnode_get_child(node, "userinfo"); + if ((child != NULL) && ((data = xmlnode_get_data(child)) != NULL)) + { + gaim_account_set_user_info(ret, data); + g_free(data); + } + + /* Read the buddyicon */ + child = xmlnode_get_child(node, "buddyicon"); + if ((child != NULL) && ((data = xmlnode_get_data(child)) != NULL)) + { + gaim_account_set_buddy_icon(ret, data); + g_free(data); + } + + /* Read settings (both core and UI) */ + for (child = xmlnode_get_child(node, "settings"); child != NULL; + child = xmlnode_get_next_twin(child)) + { + parse_settings(child, ret); + } + + /* Read proxy */ + child = xmlnode_get_child(node, "proxy"); + if (child != NULL) + { + parse_proxy_info(child, ret); + } + + return ret; +} + +static void +load_accounts(void) +{ + xmlnode *node, *child; + + accounts_loaded = TRUE; + + node = gaim_util_read_xml_from_file("accounts.xml", _("accounts")); + + if (node == NULL) + return; + + for (child = xmlnode_get_child(node, "account"); child != NULL; + child = xmlnode_get_next_twin(child)) + { + GaimAccount *new_acct; + new_acct = parse_account(child); + gaim_accounts_add(new_acct); + } + + xmlnode_free(node); +} + + +static void +delete_setting(void *data) +{ + GaimAccountSetting *setting = (GaimAccountSetting *)data; + + g_free(setting->ui); + + if (setting->type == GAIM_PREF_STRING) + g_free(setting->value.string); + + g_free(setting); +} + +GaimAccount * +gaim_account_new(const char *username, const char *protocol_id) +{ + GaimAccount *account = NULL; + GaimPlugin *prpl = NULL; + GaimPluginProtocolInfo *prpl_info = NULL; + GaimStatusType *status_type; + + g_return_val_if_fail(username != NULL, NULL); + g_return_val_if_fail(protocol_id != NULL, NULL); + + account = gaim_accounts_find(username, protocol_id); + + if (account != NULL) + return account; + + account = g_new0(GaimAccount, 1); + GAIM_DBUS_REGISTER_POINTER(account, GaimAccount); + + gaim_account_set_username(account, username); + + gaim_account_set_protocol_id(account, protocol_id); + + account->settings = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, delete_setting); + account->ui_settings = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, (GDestroyNotify)g_hash_table_destroy); + account->system_log = NULL; + /* 0 is not a valid privacy setting */ + account->perm_deny = GAIM_PRIVACY_ALLOW_ALL; + + account->presence = gaim_presence_new_for_account(account); + + prpl = gaim_find_prpl(protocol_id); + + if (prpl == NULL) + return account; + + prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(prpl); + if (prpl_info != NULL && prpl_info->status_types != NULL) + gaim_account_set_status_types(account, prpl_info->status_types(account)); + + status_type = gaim_account_get_status_type_with_primitive(account, GAIM_STATUS_AVAILABLE); + if (status_type != NULL) + gaim_presence_set_status_active(account->presence, + gaim_status_type_get_id(status_type), + TRUE); + else + gaim_presence_set_status_active(account->presence, + "offline", + TRUE); + + return account; +} + +void +gaim_account_destroy(GaimAccount *account) +{ + GList *l; + + g_return_if_fail(account != NULL); + + gaim_debug_info("account", "Destroying account %p\n", account); + + for (l = gaim_get_conversations(); l != NULL; l = l->next) + { + GaimConversation *conv = (GaimConversation *)l->data; + + if (gaim_conversation_get_account(conv) == account) + gaim_conversation_set_account(conv, NULL); + } + + g_free(account->username); + g_free(account->alias); + g_free(account->password); + g_free(account->user_info); + g_free(account->protocol_id); + + g_hash_table_destroy(account->settings); + g_hash_table_destroy(account->ui_settings); + + gaim_account_set_status_types(account, NULL); + + gaim_presence_destroy(account->presence); + + if(account->system_log) + gaim_log_free(account->system_log); + + GAIM_DBUS_UNREGISTER_POINTER(account); + g_free(account); +} + +void +gaim_account_register(GaimAccount *account) +{ + g_return_if_fail(account != NULL); + + gaim_debug_info("account", "Registering account %s\n", + gaim_account_get_username(account)); + + gaim_connection_new(account, TRUE, NULL); +} + +static void +request_password_ok_cb(GaimAccount *account, GaimRequestFields *fields) +{ + const char *entry; + gboolean remember; + + entry = gaim_request_fields_get_string(fields, "password"); + remember = gaim_request_fields_get_bool(fields, "remember"); + + if (!entry || !*entry) + { + gaim_notify_error(account, NULL, _("Password is required to sign on."), NULL); + return; + } + + if(remember) + gaim_account_set_remember_password(account, TRUE); + + gaim_account_set_password(account, entry); + + gaim_connection_new(account, FALSE, entry); +} + +static void +request_password(GaimAccount *account) +{ + gchar *primary; + const gchar *username; + GaimRequestFieldGroup *group; + GaimRequestField *field; + GaimRequestFields *fields; + + /* Close any previous password request windows */ + gaim_request_close_with_handle(account); + + username = gaim_account_get_username(account); + primary = g_strdup_printf(_("Enter password for %s (%s)"), username, + gaim_account_get_protocol_name(account)); + + fields = gaim_request_fields_new(); + group = gaim_request_field_group_new(NULL); + gaim_request_fields_add_group(fields, group); + + field = gaim_request_field_string_new("password", _("Enter Password"), NULL, FALSE); + gaim_request_field_string_set_masked(field, TRUE); + gaim_request_field_set_required(field, TRUE); + gaim_request_field_group_add_field(group, field); + + field = gaim_request_field_bool_new("remember", _("Save password"), FALSE); + gaim_request_field_group_add_field(group, field); + + gaim_request_fields(account, + NULL, + primary, + NULL, + fields, + _("OK"), G_CALLBACK(request_password_ok_cb), + _("Cancel"), NULL, + account); + g_free(primary); +} + +void +gaim_account_connect(GaimAccount *account) +{ + GaimPlugin *prpl; + GaimPluginProtocolInfo *prpl_info; + const char *password; + + g_return_if_fail(account != NULL); + + gaim_debug_info("account", "Connecting to account %s\n", + gaim_account_get_username(account)); + + if (!gaim_account_get_enabled(account, gaim_core_get_ui())) + return; + + prpl = gaim_find_prpl(gaim_account_get_protocol_id(account)); + if (prpl == NULL) + { + gchar *message; + + message = g_strdup_printf(_("Missing protocol plugin for %s"), + gaim_account_get_username(account)); + gaim_notify_error(account, _("Connection Error"), message, NULL); + g_free(message); + return; + } + + prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(prpl); + password = gaim_account_get_password(account); + if ((password == NULL) && + !(prpl_info->options & OPT_PROTO_NO_PASSWORD) && + !(prpl_info->options & OPT_PROTO_PASSWORD_OPTIONAL)) + request_password(account); + else + gaim_connection_new(account, FALSE, password); +} + +void +gaim_account_disconnect(GaimAccount *account) +{ + GaimConnection *gc; + + g_return_if_fail(account != NULL); + g_return_if_fail(!gaim_account_is_disconnected(account)); + + gaim_debug_info("account", "Disconnecting account %p\n", account); + + account->disconnecting = TRUE; + + gc = gaim_account_get_connection(account); + gaim_connection_destroy(gc); + if (!gaim_account_get_remember_password(account)) + gaim_account_set_password(account, NULL); + gaim_account_set_connection(account, NULL); + + account->disconnecting = FALSE; +} + +void +gaim_account_notify_added(GaimAccount *account, const char *remote_user, + const char *id, const char *alias, + const char *message) +{ + GaimAccountUiOps *ui_ops; + + g_return_if_fail(account != NULL); + g_return_if_fail(remote_user != NULL); + + ui_ops = gaim_accounts_get_ui_ops(); + + if (ui_ops != NULL && ui_ops->notify_added != NULL) + ui_ops->notify_added(account, remote_user, id, alias, message); +} + +void +gaim_account_request_add(GaimAccount *account, const char *remote_user, + const char *id, const char *alias, + const char *message) +{ + GaimAccountUiOps *ui_ops; + + g_return_if_fail(account != NULL); + g_return_if_fail(remote_user != NULL); + + ui_ops = gaim_accounts_get_ui_ops(); + + if (ui_ops != NULL && ui_ops->request_add != NULL) + ui_ops->request_add(account, remote_user, id, alias, message); +} + +static void +change_password_cb(GaimAccount *account, GaimRequestFields *fields) +{ + const char *orig_pass, *new_pass_1, *new_pass_2; + + orig_pass = gaim_request_fields_get_string(fields, "password"); + new_pass_1 = gaim_request_fields_get_string(fields, "new_password_1"); + new_pass_2 = gaim_request_fields_get_string(fields, "new_password_2"); + + if (g_utf8_collate(new_pass_1, new_pass_2)) + { + gaim_notify_error(account, NULL, + _("New passwords do not match."), NULL); + + return; + } + + if (orig_pass == NULL || new_pass_1 == NULL || new_pass_2 == NULL || + *orig_pass == '\0' || *new_pass_1 == '\0' || *new_pass_2 == '\0') + { + gaim_notify_error(account, NULL, + _("Fill out all fields completely."), NULL); + return; + } + + gaim_account_change_password(account, orig_pass, new_pass_1); +} + +void +gaim_account_request_change_password(GaimAccount *account) +{ + GaimRequestFields *fields; + GaimRequestFieldGroup *group; + GaimRequestField *field; + char primary[256]; + + g_return_if_fail(account != NULL); + g_return_if_fail(gaim_account_is_connected(account)); + + fields = gaim_request_fields_new(); + + group = gaim_request_field_group_new(NULL); + gaim_request_fields_add_group(fields, group); + + field = gaim_request_field_string_new("password", _("Original password"), + NULL, FALSE); + gaim_request_field_string_set_masked(field, TRUE); + gaim_request_field_set_required(field, TRUE); + gaim_request_field_group_add_field(group, field); + + field = gaim_request_field_string_new("new_password_1", + _("New password"), + NULL, FALSE); + gaim_request_field_string_set_masked(field, TRUE); + gaim_request_field_set_required(field, TRUE); + gaim_request_field_group_add_field(group, field); + + field = gaim_request_field_string_new("new_password_2", + _("New password (again)"), + NULL, FALSE); + gaim_request_field_string_set_masked(field, TRUE); + gaim_request_field_set_required(field, TRUE); + gaim_request_field_group_add_field(group, field); + + g_snprintf(primary, sizeof(primary), _("Change password for %s"), + gaim_account_get_username(account)); + + /* I'm sticking this somewhere in the code: bologna */ + + gaim_request_fields(gaim_account_get_connection(account), + NULL, + primary, + _("Please enter your current password and your " + "new password."), + fields, + _("OK"), G_CALLBACK(change_password_cb), + _("Cancel"), NULL, + account); +} + +static void +set_user_info_cb(GaimAccount *account, const char *user_info) +{ + GaimConnection *gc; + + gaim_account_set_user_info(account, user_info); + + gc = gaim_account_get_connection(account); + + if (gc != NULL) + serv_set_info(gc, user_info); +} + +void +gaim_account_request_change_user_info(GaimAccount *account) +{ + GaimConnection *gc; + char primary[256]; + + g_return_if_fail(account != NULL); + g_return_if_fail(gaim_account_is_connected(account)); + + gc = gaim_account_get_connection(account); + + g_snprintf(primary, sizeof(primary), + _("Change user information for %s"), + gaim_account_get_username(account)); + + gaim_request_input(gc, _("Set User Info"), primary, NULL, + gaim_account_get_user_info(account), + TRUE, FALSE, ((gc != NULL) && + (gc->flags & GAIM_CONNECTION_HTML) ? "html" : NULL), + _("Save"), G_CALLBACK(set_user_info_cb), + _("Cancel"), NULL, account); +} + +void +gaim_account_set_username(GaimAccount *account, const char *username) +{ + g_return_if_fail(account != NULL); + + g_free(account->username); + account->username = g_strdup(username); + + schedule_accounts_save(); +} + +void +gaim_account_set_password(GaimAccount *account, const char *password) +{ + g_return_if_fail(account != NULL); + + g_free(account->password); + account->password = g_strdup(password); + + schedule_accounts_save(); +} + +void +gaim_account_set_alias(GaimAccount *account, const char *alias) +{ + g_return_if_fail(account != NULL); + + /* + * Do nothing if alias and account->alias are both NULL. Or if + * they're the exact same string. + */ + if (alias == account->alias) + return; + + if ((!alias && account->alias) || (alias && !account->alias) || + g_utf8_collate(account->alias, alias)) + { + char *old = account->alias; + + account->alias = g_strdup(alias); + gaim_signal_emit(gaim_accounts_get_handle(), "account-alias-changed", + account, old); + g_free(old); + + schedule_accounts_save(); + } +} + +void +gaim_account_set_user_info(GaimAccount *account, const char *user_info) +{ + g_return_if_fail(account != NULL); + + g_free(account->user_info); + account->user_info = g_strdup(user_info); + + schedule_accounts_save(); +} + +void +gaim_account_set_buddy_icon(GaimAccount *account, const char *icon) +{ + g_return_if_fail(account != NULL); + + /* Delete an existing icon from the cache. */ + if (account->buddy_icon != NULL && (icon == NULL || strcmp(account->buddy_icon, icon))) + { + const char *dirname = gaim_buddy_icons_get_cache_dir(); + struct stat st; + + if (g_stat(account->buddy_icon, &st) == 0) + { + /* The file exists. This is a full path. */ + + /* XXX: This is a hack so we only delete the file if it's + * in the cache dir. Otherwise, people who upgrade (who + * may have buddy icon filenames set outside of the cache + * dir) could lose files. */ + if (!strncmp(dirname, account->buddy_icon, strlen(dirname))) + g_unlink(account->buddy_icon); + } + else + { + char *filename = g_build_filename(dirname, account->buddy_icon, NULL); + g_unlink(filename); + g_free(filename); + } + } + + g_free(account->buddy_icon); + account->buddy_icon = g_strdup(icon); + if (gaim_account_is_connected(account)) + { + char *filename = gaim_buddy_icons_get_full_path(icon); + serv_set_buddyicon(gaim_account_get_connection(account), filename); + g_free(filename); + } + + schedule_accounts_save(); +} + +void +gaim_account_set_protocol_id(GaimAccount *account, const char *protocol_id) +{ + g_return_if_fail(account != NULL); + g_return_if_fail(protocol_id != NULL); + + g_free(account->protocol_id); + account->protocol_id = g_strdup(protocol_id); + + schedule_accounts_save(); +} + +void +gaim_account_set_connection(GaimAccount *account, GaimConnection *gc) +{ + g_return_if_fail(account != NULL); + + account->gc = gc; +} + +void +gaim_account_set_remember_password(GaimAccount *account, gboolean value) +{ + g_return_if_fail(account != NULL); + + account->remember_pass = value; + + schedule_accounts_save(); +} + +void +gaim_account_set_check_mail(GaimAccount *account, gboolean value) +{ + g_return_if_fail(account != NULL); + + gaim_account_set_bool(account, "check-mail", value); +} + +void +gaim_account_set_enabled(GaimAccount *account, const char *ui, + gboolean value) +{ + GaimConnection *gc; + gboolean was_enabled = FALSE; + + g_return_if_fail(account != NULL); + g_return_if_fail(ui != NULL); + + was_enabled = gaim_account_get_enabled(account, ui); + + gaim_account_set_ui_bool(account, ui, "auto-login", value); + gc = gaim_account_get_connection(account); + + if(was_enabled && !value) + gaim_signal_emit(gaim_accounts_get_handle(), "account-disabled", account); + else if(!was_enabled && value) + gaim_signal_emit(gaim_accounts_get_handle(), "account-enabled", account); + + if ((gc != NULL) && (gc->wants_to_die == TRUE)) + return; + + if (value && gaim_presence_is_online(account->presence)) + gaim_account_connect(account); + else if (!value && !gaim_account_is_disconnected(account)) + gaim_account_disconnect(account); +} + +void +gaim_account_set_proxy_info(GaimAccount *account, GaimProxyInfo *info) +{ + g_return_if_fail(account != NULL); + + if (account->proxy_info != NULL) + gaim_proxy_info_destroy(account->proxy_info); + + account->proxy_info = info; + + schedule_accounts_save(); +} + +void +gaim_account_set_status_types(GaimAccount *account, GList *status_types) +{ + g_return_if_fail(account != NULL); + + /* Old with the old... */ + if (account->status_types != NULL) + { + g_list_foreach(account->status_types, (GFunc)gaim_status_type_destroy, NULL); + g_list_free(account->status_types); + } + + /* In with the new... */ + account->status_types = status_types; +} + +void +gaim_account_set_status(GaimAccount *account, const char *status_id, + gboolean active, ...) +{ + va_list args; + + va_start(args, active); + gaim_account_set_status_vargs(account, status_id, active, args); + va_end(args); +} + +void +gaim_account_set_status_vargs(GaimAccount *account, const char *status_id, + gboolean active, va_list args) +{ + GList *attrs = NULL; + const gchar *id; + gpointer data; + + if (args != NULL) + { + while ((id = va_arg(args, const char *)) != NULL) + { + attrs = g_list_append(attrs, (char *)id); + data = va_arg(args, void *); + attrs = g_list_append(attrs, data); + } + } + gaim_account_set_status_list(account, status_id, active, attrs); + g_list_free(attrs); +} + +void +gaim_account_set_status_list(GaimAccount *account, const char *status_id, + gboolean active, GList *attrs) +{ + GaimStatus *status; + + g_return_if_fail(account != NULL); + g_return_if_fail(status_id != NULL); + + status = gaim_account_get_status(account, status_id); + if (status == NULL) + { + gaim_debug_error("account", + "Invalid status ID %s for account %s (%s)\n", + status_id, gaim_account_get_username(account), + gaim_account_get_protocol_id(account)); + return; + } + + if (active || gaim_status_is_independent(status)) + gaim_status_set_active_with_attrs_list(status, active, attrs); + + /* + * Our current statuses are saved to accounts.xml (so that when we + * reconnect, we go back to the previous status). + */ + schedule_accounts_save(); +} + +void +gaim_account_clear_settings(GaimAccount *account) +{ + g_return_if_fail(account != NULL); + + g_hash_table_destroy(account->settings); + + account->settings = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, delete_setting); +} + +void +gaim_account_set_int(GaimAccount *account, const char *name, int value) +{ + GaimAccountSetting *setting; + + g_return_if_fail(account != NULL); + g_return_if_fail(name != NULL); + + setting = g_new0(GaimAccountSetting, 1); + + setting->type = GAIM_PREF_INT; + setting->value.integer = value; + + g_hash_table_insert(account->settings, g_strdup(name), setting); + + schedule_accounts_save(); +} + +void +gaim_account_set_string(GaimAccount *account, const char *name, + const char *value) +{ + GaimAccountSetting *setting; + + g_return_if_fail(account != NULL); + g_return_if_fail(name != NULL); + + setting = g_new0(GaimAccountSetting, 1); + + setting->type = GAIM_PREF_STRING; + setting->value.string = g_strdup(value); + + g_hash_table_insert(account->settings, g_strdup(name), setting); + + schedule_accounts_save(); +} + +void +gaim_account_set_bool(GaimAccount *account, const char *name, gboolean value) +{ + GaimAccountSetting *setting; + + g_return_if_fail(account != NULL); + g_return_if_fail(name != NULL); + + setting = g_new0(GaimAccountSetting, 1); + + setting->type = GAIM_PREF_BOOLEAN; + setting->value.bool = value; + + g_hash_table_insert(account->settings, g_strdup(name), setting); + + schedule_accounts_save(); +} + +static GHashTable * +get_ui_settings_table(GaimAccount *account, const char *ui) +{ + GHashTable *table; + + table = g_hash_table_lookup(account->ui_settings, ui); + + if (table == NULL) { + table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, + delete_setting); + g_hash_table_insert(account->ui_settings, g_strdup(ui), table); + } + + return table; +} + +void +gaim_account_set_ui_int(GaimAccount *account, const char *ui, + const char *name, int value) +{ + GaimAccountSetting *setting; + GHashTable *table; + + g_return_if_fail(account != NULL); + g_return_if_fail(ui != NULL); + g_return_if_fail(name != NULL); + + setting = g_new0(GaimAccountSetting, 1); + + setting->type = GAIM_PREF_INT; + setting->ui = g_strdup(ui); + setting->value.integer = value; + + table = get_ui_settings_table(account, ui); + + g_hash_table_insert(table, g_strdup(name), setting); + + schedule_accounts_save(); +} + +void +gaim_account_set_ui_string(GaimAccount *account, const char *ui, + const char *name, const char *value) +{ + GaimAccountSetting *setting; + GHashTable *table; + + g_return_if_fail(account != NULL); + g_return_if_fail(ui != NULL); + g_return_if_fail(name != NULL); + + setting = g_new0(GaimAccountSetting, 1); + + setting->type = GAIM_PREF_STRING; + setting->ui = g_strdup(ui); + setting->value.string = g_strdup(value); + + table = get_ui_settings_table(account, ui); + + g_hash_table_insert(table, g_strdup(name), setting); + + schedule_accounts_save(); +} + +void +gaim_account_set_ui_bool(GaimAccount *account, const char *ui, + const char *name, gboolean value) +{ + GaimAccountSetting *setting; + GHashTable *table; + + g_return_if_fail(account != NULL); + g_return_if_fail(ui != NULL); + g_return_if_fail(name != NULL); + + setting = g_new0(GaimAccountSetting, 1); + + setting->type = GAIM_PREF_BOOLEAN; + setting->ui = g_strdup(ui); + setting->value.bool = value; + + table = get_ui_settings_table(account, ui); + + g_hash_table_insert(table, g_strdup(name), setting); + + schedule_accounts_save(); +} + +static GaimConnectionState +gaim_account_get_state(const GaimAccount *account) +{ + GaimConnection *gc; + + g_return_val_if_fail(account != NULL, GAIM_DISCONNECTED); + + gc = gaim_account_get_connection(account); + if (!gc) + return GAIM_DISCONNECTED; + + return gaim_connection_get_state(gc); +} + +gboolean +gaim_account_is_connected(const GaimAccount *account) +{ + return (gaim_account_get_state(account) == GAIM_CONNECTED); +} + +gboolean +gaim_account_is_connecting(const GaimAccount *account) +{ + return (gaim_account_get_state(account) == GAIM_CONNECTING); +} + +gboolean +gaim_account_is_disconnected(const GaimAccount *account) +{ + return (gaim_account_get_state(account) == GAIM_DISCONNECTED); +} + +const char * +gaim_account_get_username(const GaimAccount *account) +{ + g_return_val_if_fail(account != NULL, NULL); + + return account->username; +} + +const char * +gaim_account_get_password(const GaimAccount *account) +{ + g_return_val_if_fail(account != NULL, NULL); + + return account->password; +} + +const char * +gaim_account_get_alias(const GaimAccount *account) +{ + g_return_val_if_fail(account != NULL, NULL); + + return account->alias; +} + +const char * +gaim_account_get_user_info(const GaimAccount *account) +{ + g_return_val_if_fail(account != NULL, NULL); + + return account->user_info; +} + +const char * +gaim_account_get_buddy_icon(const GaimAccount *account) +{ + g_return_val_if_fail(account != NULL, NULL); + + return account->buddy_icon; +} + +const char * +gaim_account_get_protocol_id(const GaimAccount *account) +{ + g_return_val_if_fail(account != NULL, NULL); + + return account->protocol_id; +} + +const char * +gaim_account_get_protocol_name(const GaimAccount *account) +{ + GaimPlugin *p; + + g_return_val_if_fail(account != NULL, NULL); + + p = gaim_find_prpl(gaim_account_get_protocol_id(account)); + + return ((p && p->info->name) ? _(p->info->name) : _("Unknown")); +} + +GaimConnection * +gaim_account_get_connection(const GaimAccount *account) +{ + g_return_val_if_fail(account != NULL, NULL); + + return account->gc; +} + +gboolean +gaim_account_get_remember_password(const GaimAccount *account) +{ + g_return_val_if_fail(account != NULL, FALSE); + + return account->remember_pass; +} + +gboolean +gaim_account_get_check_mail(const GaimAccount *account) +{ + g_return_val_if_fail(account != NULL, FALSE); + + return gaim_account_get_bool(account, "check-mail", FALSE); +} + +gboolean +gaim_account_get_enabled(const GaimAccount *account, const char *ui) +{ + g_return_val_if_fail(account != NULL, FALSE); + g_return_val_if_fail(ui != NULL, FALSE); + + return gaim_account_get_ui_bool(account, ui, "auto-login", FALSE); +} + +GaimProxyInfo * +gaim_account_get_proxy_info(const GaimAccount *account) +{ + g_return_val_if_fail(account != NULL, NULL); + + return account->proxy_info; +} + +GaimStatus * +gaim_account_get_active_status(const GaimAccount *account) +{ + g_return_val_if_fail(account != NULL, NULL); + + return gaim_presence_get_active_status(account->presence); +} + +GaimStatus * +gaim_account_get_status(const GaimAccount *account, const char *status_id) +{ + g_return_val_if_fail(account != NULL, NULL); + g_return_val_if_fail(status_id != NULL, NULL); + + return gaim_presence_get_status(account->presence, status_id); +} + +GaimStatusType * +gaim_account_get_status_type(const GaimAccount *account, const char *id) +{ + const GList *l; + + g_return_val_if_fail(account != NULL, NULL); + g_return_val_if_fail(id != NULL, NULL); + + for (l = gaim_account_get_status_types(account); l != NULL; l = l->next) + { + GaimStatusType *status_type = (GaimStatusType *)l->data; + + if (!strcmp(gaim_status_type_get_id(status_type), id)) + return status_type; + } + + return NULL; +} + +GaimStatusType * +gaim_account_get_status_type_with_primitive(const GaimAccount *account, GaimStatusPrimitive primitive) +{ + const GList *l; + + g_return_val_if_fail(account != NULL, NULL); + + for (l = gaim_account_get_status_types(account); l != NULL; l = l->next) + { + GaimStatusType *status_type = (GaimStatusType *)l->data; + + if (gaim_status_type_get_primitive(status_type) == primitive) + return status_type; + } + + return NULL; +} + +GaimPresence * +gaim_account_get_presence(const GaimAccount *account) +{ + g_return_val_if_fail(account != NULL, NULL); + + return account->presence; +} + +gboolean +gaim_account_is_status_active(const GaimAccount *account, + const char *status_id) +{ + g_return_val_if_fail(account != NULL, FALSE); + g_return_val_if_fail(status_id != NULL, FALSE); + + return gaim_presence_is_status_active(account->presence, status_id); +} + +const GList * +gaim_account_get_status_types(const GaimAccount *account) +{ + g_return_val_if_fail(account != NULL, NULL); + + return account->status_types; +} + +int +gaim_account_get_int(const GaimAccount *account, const char *name, + int default_value) +{ + GaimAccountSetting *setting; + + g_return_val_if_fail(account != NULL, default_value); + g_return_val_if_fail(name != NULL, default_value); + + setting = g_hash_table_lookup(account->settings, name); + + if (setting == NULL) + return default_value; + + g_return_val_if_fail(setting->type == GAIM_PREF_INT, default_value); + + return setting->value.integer; +} + +const char * +gaim_account_get_string(const GaimAccount *account, const char *name, + const char *default_value) +{ + GaimAccountSetting *setting; + + g_return_val_if_fail(account != NULL, default_value); + g_return_val_if_fail(name != NULL, default_value); + + setting = g_hash_table_lookup(account->settings, name); + + if (setting == NULL) + return default_value; + + g_return_val_if_fail(setting->type == GAIM_PREF_STRING, default_value); + + return setting->value.string; +} + +gboolean +gaim_account_get_bool(const GaimAccount *account, const char *name, + gboolean default_value) +{ + GaimAccountSetting *setting; + + g_return_val_if_fail(account != NULL, default_value); + g_return_val_if_fail(name != NULL, default_value); + + setting = g_hash_table_lookup(account->settings, name); + + if (setting == NULL) + return default_value; + + g_return_val_if_fail(setting->type == GAIM_PREF_BOOLEAN, default_value); + + return setting->value.bool; +} + +int +gaim_account_get_ui_int(const GaimAccount *account, const char *ui, + const char *name, int default_value) +{ + GaimAccountSetting *setting; + GHashTable *table; + + g_return_val_if_fail(account != NULL, default_value); + g_return_val_if_fail(ui != NULL, default_value); + g_return_val_if_fail(name != NULL, default_value); + + if ((table = g_hash_table_lookup(account->ui_settings, ui)) == NULL) + return default_value; + + if ((setting = g_hash_table_lookup(table, name)) == NULL) + return default_value; + + g_return_val_if_fail(setting->type == GAIM_PREF_INT, default_value); + + return setting->value.integer; +} + +const char * +gaim_account_get_ui_string(const GaimAccount *account, const char *ui, + const char *name, const char *default_value) +{ + GaimAccountSetting *setting; + GHashTable *table; + + g_return_val_if_fail(account != NULL, default_value); + g_return_val_if_fail(ui != NULL, default_value); + g_return_val_if_fail(name != NULL, default_value); + + if ((table = g_hash_table_lookup(account->ui_settings, ui)) == NULL) + return default_value; + + if ((setting = g_hash_table_lookup(table, name)) == NULL) + return default_value; + + g_return_val_if_fail(setting->type == GAIM_PREF_STRING, default_value); + + return setting->value.string; +} + +gboolean +gaim_account_get_ui_bool(const GaimAccount *account, const char *ui, + const char *name, gboolean default_value) +{ + GaimAccountSetting *setting; + GHashTable *table; + + g_return_val_if_fail(account != NULL, default_value); + g_return_val_if_fail(ui != NULL, default_value); + g_return_val_if_fail(name != NULL, default_value); + + if ((table = g_hash_table_lookup(account->ui_settings, ui)) == NULL) + return default_value; + + if ((setting = g_hash_table_lookup(table, name)) == NULL) + return default_value; + + g_return_val_if_fail(setting->type == GAIM_PREF_BOOLEAN, default_value); + + return setting->value.bool; +} + +GaimLog * +gaim_account_get_log(GaimAccount *account, gboolean create) +{ + g_return_val_if_fail(account != NULL, NULL); + + if(!account->system_log && create){ + GaimPresence *presence; + int login_time; + + presence = gaim_account_get_presence(account); + login_time = gaim_presence_get_login_time(presence); + + account->system_log = gaim_log_new(GAIM_LOG_SYSTEM, + gaim_account_get_username(account), account, NULL, + (login_time != 0) ? login_time : time(NULL), NULL); + } + + return account->system_log; +} + +void +gaim_account_destroy_log(GaimAccount *account) +{ + g_return_if_fail(account != NULL); + + if(account->system_log){ + gaim_log_free(account->system_log); + account->system_log = NULL; + } +} + +void +gaim_account_add_buddy(GaimAccount *account, GaimBuddy *buddy) +{ + GaimPluginProtocolInfo *prpl_info = NULL; + GaimConnection *gc = gaim_account_get_connection(account); + + if (gc != NULL && gc->prpl != NULL) + prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl); + + if (prpl_info != NULL && g_list_find(gaim_connections_get_all(), gc) && + prpl_info->add_buddy != NULL) + prpl_info->add_buddy(gc, buddy, gaim_buddy_get_group(buddy)); +} + +void +gaim_account_add_buddies(GaimAccount *account, GList *buddies) +{ + GaimPluginProtocolInfo *prpl_info = NULL; + GaimConnection *gc = gaim_account_get_connection(account); + + if (gc != NULL && gc->prpl != NULL) + prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl); + + if (prpl_info && g_list_find(gaim_connections_get_all(), gc)) { + GList *cur, *groups = NULL; + + /* Make a list of what group each buddy is in */ + for (cur = buddies; cur != NULL; cur = cur->next) { + GaimBlistNode *node = cur->data; + groups = g_list_append(groups, node->parent->parent); + } + + if (prpl_info->add_buddies != NULL) + prpl_info->add_buddies(gc, buddies, groups); + else if (prpl_info->add_buddy != NULL) { + GList *curb = buddies, *curg = groups; + + while ((curb != NULL) && (curg != NULL)) { + prpl_info->add_buddy(gc, curb->data, curg->data); + curb = curb->next; + curg = curg->next; + } + } + + g_list_free(groups); + } +} + +void +gaim_account_remove_buddy(GaimAccount *account, GaimBuddy *buddy, + GaimGroup *group) +{ + GaimPluginProtocolInfo *prpl_info = NULL; + GaimConnection *gc = gaim_account_get_connection(account); + + if (gc != NULL && gc->prpl != NULL) + prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl); + + if (prpl_info && g_list_find(gaim_connections_get_all(), gc) && prpl_info->remove_buddy) + prpl_info->remove_buddy(gc, buddy, group); +} + +void +gaim_account_remove_buddies(GaimAccount *account, GList *buddies, GList *groups) +{ + GaimPluginProtocolInfo *prpl_info = NULL; + GaimConnection *gc = gaim_account_get_connection(account); + + if (!g_list_find(gaim_connections_get_all(), gc)) + return; + + if (gc != NULL && gc->prpl != NULL) + prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl); + + if (prpl_info && g_list_find(gaim_connections_get_all(), gc)) { + if (prpl_info->remove_buddies) + prpl_info->remove_buddies(gc, buddies, groups); + else { + GList *curb = buddies; + GList *curg = groups; + while ((curb != NULL) && (curg != NULL)) { + gaim_account_remove_buddy(account, curb->data, curg->data); + curb = curb->next; + curg = curg->next; + } + } + } +} + +void +gaim_account_remove_group(GaimAccount *account, GaimGroup *group) +{ + GaimPluginProtocolInfo *prpl_info = NULL; + GaimConnection *gc = gaim_account_get_connection(account); + + if (gc != NULL && gc->prpl != NULL) + prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl); + + if (prpl_info && g_list_find(gaim_connections_get_all(), gc) && prpl_info->remove_group) + prpl_info->remove_group(gc, group); +} + +void +gaim_account_change_password(GaimAccount *account, const char *orig_pw, + const char *new_pw) +{ + GaimPluginProtocolInfo *prpl_info = NULL; + GaimConnection *gc = gaim_account_get_connection(account); + + gaim_account_set_password(account, new_pw); + + if (gc != NULL && gc->prpl != NULL) + prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl); + + if (prpl_info && g_list_find(gaim_connections_get_all(), gc) && prpl_info->change_passwd) + prpl_info->change_passwd(gc, orig_pw, new_pw); +} + +gboolean gaim_account_supports_offline_message(GaimAccount *account, GaimBuddy *buddy) +{ + GaimConnection *gc; + GaimPluginProtocolInfo *prpl_info; + + g_return_val_if_fail(account, FALSE); + g_return_val_if_fail(buddy, FALSE); + + gc = gaim_account_get_connection(account); + if (gc == NULL) + return FALSE; + + prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl); + + if (!prpl_info || !prpl_info->offline_message) + return FALSE; + return prpl_info->offline_message(buddy); +} + +void +gaim_accounts_add(GaimAccount *account) +{ + g_return_if_fail(account != NULL); + + if (g_list_find(accounts, account) != NULL) + return; + + accounts = g_list_append(accounts, account); + + schedule_accounts_save(); + + gaim_signal_emit(gaim_accounts_get_handle(), "account-added", account); +} + +void +gaim_accounts_remove(GaimAccount *account) +{ + g_return_if_fail(account != NULL); + + accounts = g_list_remove(accounts, account); + + schedule_accounts_save(); + + gaim_signal_emit(gaim_accounts_get_handle(), "account-removed", account); +} + +void +gaim_accounts_delete(GaimAccount *account) +{ + GaimBlistNode *gnode, *cnode, *bnode; + + g_return_if_fail(account != NULL); + + if (gaim_account_is_connected(account)) + gaim_account_disconnect(account); + + gaim_notify_close_with_handle(account); + gaim_request_close_with_handle(account); + + gaim_accounts_remove(account); + + /* Remove this account's buddies */ + for (gnode = gaim_get_blist()->root; gnode != NULL; gnode = gnode->next) { + if (!GAIM_BLIST_NODE_IS_GROUP(gnode)) + continue; + + cnode = gnode->child; + while (cnode) { + GaimBlistNode *cnode_next = cnode->next; + + if(GAIM_BLIST_NODE_IS_CONTACT(cnode)) { + bnode = cnode->child; + while (bnode) { + GaimBlistNode *bnode_next = bnode->next; + + if (GAIM_BLIST_NODE_IS_BUDDY(bnode)) { + GaimBuddy *b = (GaimBuddy *)bnode; + + if (b->account == account) + gaim_blist_remove_buddy(b); + } + bnode = bnode_next; + } + } else if (GAIM_BLIST_NODE_IS_CHAT(cnode)) { + GaimChat *c = (GaimChat *)cnode; + + if (c->account == account) + gaim_blist_remove_chat(c); + } + cnode = cnode_next; + } + } + + /* Remove this account's pounces */ + gaim_pounce_destroy_all_by_account(account); + + /* This will cause the deletion of an old buddy icon. */ + gaim_account_set_buddy_icon(account, NULL); + + gaim_account_destroy(account); +} + +void +gaim_accounts_reorder(GaimAccount *account, gint new_index) +{ + gint index; + GList *l; + + g_return_if_fail(account != NULL); + g_return_if_fail(new_index <= g_list_length(accounts)); + + index = g_list_index(accounts, account); + + if (index == -1) { + gaim_debug_error("account", + "Unregistered account (%s) discovered during reorder!\n", + gaim_account_get_username(account)); + return; + } + + l = g_list_nth(accounts, index); + + if (new_index > index) + new_index--; + + /* Remove the old one. */ + accounts = g_list_delete_link(accounts, l); + + /* Insert it where it should go. */ + accounts = g_list_insert(accounts, account, new_index); + + schedule_accounts_save(); +} + +GList * +gaim_accounts_get_all(void) +{ + return accounts; +} + +GList * +gaim_accounts_get_all_active(void) +{ + GList *list = NULL; + GList *all = gaim_accounts_get_all(); + + while (all != NULL) { + GaimAccount *account = all->data; + + if (gaim_account_get_enabled(account, gaim_core_get_ui())) + list = g_list_append(list, account); + + all = all->next; + } + + return list; +} + +GaimAccount * +gaim_accounts_find(const char *name, const char *protocol_id) +{ + GaimAccount *account = NULL; + GList *l; + char *who; + + g_return_val_if_fail(name != NULL, NULL); + + who = g_strdup(gaim_normalize(NULL, name)); + + for (l = gaim_accounts_get_all(); l != NULL; l = l->next) { + account = (GaimAccount *)l->data; + + if (!strcmp(gaim_normalize(NULL, gaim_account_get_username(account)), who) && + (!protocol_id || !strcmp(account->protocol_id, protocol_id))) { + + break; + } + + account = NULL; + } + + g_free(who); + + return account; +} + +void +gaim_accounts_restore_current_statuses() +{ + GList *l; + GaimAccount *account; + + for (l = gaim_accounts_get_all(); l != NULL; l = l->next) + { + account = (GaimAccount *)l->data; + if (gaim_account_get_enabled(account, gaim_core_get_ui()) && + (gaim_presence_is_online(account->presence))) + { + gaim_account_connect(account); + } + } +} + +void +gaim_accounts_set_ui_ops(GaimAccountUiOps *ops) +{ + account_ui_ops = ops; +} + +GaimAccountUiOps * +gaim_accounts_get_ui_ops(void) +{ + return account_ui_ops; +} + +void * +gaim_accounts_get_handle(void) +{ + static int handle; + + return &handle; +} + +void +gaim_accounts_init(void) +{ + void *handle = gaim_accounts_get_handle(); + + gaim_signal_register(handle, "account-connecting", + gaim_marshal_VOID__POINTER, NULL, 1, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_ACCOUNT)); + + gaim_signal_register(handle, "account-disabled", + gaim_marshal_VOID__POINTER, NULL, 1, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_ACCOUNT)); + + gaim_signal_register(handle, "account-enabled", + gaim_marshal_VOID__POINTER, NULL, 1, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_ACCOUNT)); + + gaim_signal_register(handle, "account-setting-info", + gaim_marshal_VOID__POINTER_POINTER, NULL, 2, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_ACCOUNT), + gaim_value_new(GAIM_TYPE_STRING)); + + gaim_signal_register(handle, "account-set-info", + gaim_marshal_VOID__POINTER_POINTER, NULL, 2, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_ACCOUNT), + gaim_value_new(GAIM_TYPE_STRING)); + + gaim_signal_register(handle, "account-added", + gaim_marshal_VOID__POINTER, NULL, 1, + gaim_value_new(GAIM_TYPE_SUBTYPE, GAIM_SUBTYPE_ACCOUNT)); + + gaim_signal_register(handle, "account-removed", + gaim_marshal_VOID__POINTER, NULL, 1, + gaim_value_new(GAIM_TYPE_SUBTYPE, GAIM_SUBTYPE_ACCOUNT)); + + gaim_signal_register(handle, "account-status-changed", + gaim_marshal_VOID__POINTER_POINTER_POINTER, NULL, 3, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_ACCOUNT), + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_STATUS), + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_STATUS)); + + gaim_signal_register(handle, "account-alias-changed", + gaim_marshal_VOID__POINTER_POINTER, NULL, 2, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_ACCOUNT), + gaim_value_new(GAIM_TYPE_STRING)); + + load_accounts(); + +} + +void +gaim_accounts_uninit(void) +{ + if (save_timer != 0) + { + gaim_timeout_remove(save_timer); + save_timer = 0; + sync_accounts(); + } + + gaim_signals_unregister_by_instance(gaim_accounts_get_handle()); +} diff -r d10dda2777a9 -r b63ebf84c42b core/account.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/account.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,922 @@ +/** + * @file account.h Account API + * @ingroup core + * + * 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 + * + * @see @ref account-signals + */ +#ifndef _GAIM_ACCOUNT_H_ +#define _GAIM_ACCOUNT_H_ + +#include + +typedef struct _GaimAccountUiOps GaimAccountUiOps; +typedef struct _GaimAccount GaimAccount; + +typedef gboolean (*GaimFilterAccountFunc)(GaimAccount *account); + +#include "connection.h" +#include "log.h" +#include "proxy.h" +#include "prpl.h" +#include "status.h" + +struct _GaimAccountUiOps +{ + /* A buddy we already have added us to their buddy list. */ + void (*notify_added)(GaimAccount *account, const char *remote_user, + const char *id, const char *alias, + const char *message); + void (*status_changed)(GaimAccount *account, GaimStatus *status); + /* Someone we don't have on our list added us. Will prompt to add them. */ + void (*request_add)(GaimAccount *account, const char *remote_user, + const char *id, const char *alias, + const char *message); +}; + +struct _GaimAccount +{ + char *username; /**< The username. */ + char *alias; /**< The current alias. */ + char *password; /**< The account password. */ + char *user_info; /**< User information. */ + + char *buddy_icon; /**< The buddy icon. */ + + gboolean remember_pass; /**< Remember the password. */ + + char *protocol_id; /**< The ID of the protocol. */ + + GaimConnection *gc; /**< The connection handle. */ + gboolean disconnecting; /**< The account is currently disconnecting */ + + GHashTable *settings; /**< Protocol-specific settings. */ + GHashTable *ui_settings; /**< UI-specific settings. */ + + GaimProxyInfo *proxy_info; /**< Proxy information. This will be set */ + /* to NULL when the account inherits */ + /* proxy settings from global prefs. */ + + GSList *permit; /**< Permit list. */ + GSList *deny; /**< Deny list. */ + int perm_deny; /**< The permit/deny setting. */ + + GList *status_types; /**< Status types. */ + + GaimPresence *presence; /**< Presence. */ + GaimLog *system_log; /**< The system log */ + + void *ui_data; /**< The UI can put data here. */ +}; + +#ifdef __cplusplus +extern "C" { +#endif + +/**************************************************************************/ +/** @name Account API */ +/**************************************************************************/ +/*@{*/ + +/** + * Creates a new account. + * + * @param username The username. + * @param protocol_id The protocol ID. + * + * @return The new account. + */ +GaimAccount *gaim_account_new(const char *username, const char *protocol_id); + +/** + * Destroys an account. + * + * @param account The account to destroy. + */ +void gaim_account_destroy(GaimAccount *account); + +/** + * Connects to an account. + * + * @param account The account to connect to. + */ +void gaim_account_connect(GaimAccount *account); + +/** + * Registers an account. + * + * @param account The account to register. + */ +void gaim_account_register(GaimAccount *account); + +/** + * Disconnects from an account. + * + * @param account The account to disconnect from. + */ +void gaim_account_disconnect(GaimAccount *account); + +/** + * Notifies the user that the account was added to a remote user's + * buddy list. + * + * This will present a dialog informing the user that he was added to the + * remote user's buddy list. + * + * @param account The account that was added. + * @param remote_user The name of the user that added this account. + * @param id The optional ID of the local account. Rarely used. + * @param alias The optional alias of the user. + * @param message The optional message sent from the user adding you. + */ +void gaim_account_notify_added(GaimAccount *account, const char *remote_user, + const char *id, const char *alias, + const char *message); + +/** + * Notifies the user that the account was addded to a remote user's buddy + * list and asks ther user if they want to add the remote user to their buddy + * list. + * + * This will present a dialog informing the local user that the remote user + * added them to the remote user's buddy list and will ask if they want to add + * the remote user to the buddy list. + * + * @param account The account that was added. + * @param remote_user The name of the user that added this account. + * @param id The optional ID of the local account. Rarely used. + * @param alias The optional alias of the user. + * @param message The optional message sent from the user adding you. + */ +void gaim_account_request_add(GaimAccount *account, const char *remote_user, + const char *id, const char *alias, + const char *message); +/** + * Requests information from the user to change the account's password. + * + * @param account The account to change the password on. + */ +void gaim_account_request_change_password(GaimAccount *account); + +/** + * Requests information from the user to change the account's + * user information. + * + * @param account The account to change the user information on. + */ +void gaim_account_request_change_user_info(GaimAccount *account); + +/** + * Sets the account's username. + * + * @param account The account. + * @param username The username. + */ +void gaim_account_set_username(GaimAccount *account, const char *username); + +/** + * Sets the account's password. + * + * @param account The account. + * @param password The password. + */ +void gaim_account_set_password(GaimAccount *account, const char *password); + +/** + * Sets the account's alias. + * + * @param account The account. + * @param alias The alias. + */ +void gaim_account_set_alias(GaimAccount *account, const char *alias); + +/** + * Sets the account's user information + * + * @param account The account. + * @param user_info The user information. + */ +void gaim_account_set_user_info(GaimAccount *account, const char *user_info); + +/** + * Sets the account's buddy icon. + * + * @param account The account. + * @param icon The buddy icon file. + */ +void gaim_account_set_buddy_icon(GaimAccount *account, const char *icon); + +/** + * Sets the account's protocol ID. + * + * @param account The account. + * @param protocol_id The protocol ID. + */ +void gaim_account_set_protocol_id(GaimAccount *account, + const char *protocol_id); + +/** + * Sets the account's connection. + * + * @param account The account. + * @param gc The connection. + */ +void gaim_account_set_connection(GaimAccount *account, GaimConnection *gc); + +/** + * Sets whether or not this account should save its password. + * + * @param account The account. + * @param value @c TRUE if it should remember the password. + */ +void gaim_account_set_remember_password(GaimAccount *account, gboolean value); + +/** + * Sets whether or not this account should check for mail. + * + * @param account The account. + * @param value @c TRUE if it should check for mail. + */ +void gaim_account_set_check_mail(GaimAccount *account, gboolean value); + +/** + * Sets whether or not this account is enabled for the specified + * UI. + * + * @param account The account. + * @param ui The UI. + * @param value @c TRUE if it is enabled. + */ +void gaim_account_set_enabled(GaimAccount *account, const char *ui, + gboolean value); + +/** + * Sets the account's proxy information. + * + * @param account The account. + * @param info The proxy information. + */ +void gaim_account_set_proxy_info(GaimAccount *account, GaimProxyInfo *info); + +/** + * Sets the account's status types. + * + * @param account The account. + * @param status_types The list of status types. + */ +void gaim_account_set_status_types(GaimAccount *account, GList *status_types); + +/** + * Activates or deactivates a status. All changes to the statuses of + * an account go through this function or gaim_account_set_status_vargs + * or gaim_account_set_status_list. + * + * Only independent statuses can be deactivated with this. To deactivate + * an exclusive status, activate a different (and exclusive?) status. + * + * @param account The account. + * @param status_id The ID of the status. + * @param active The active state. + * @param ... Optional NULL-terminated attributes passed for the + * new status, in an id, value pair. + */ +void gaim_account_set_status(GaimAccount *account, const char *status_id, + gboolean active, ...); + + +/** + * Activates or deactivates a status. All changes to the statuses of + * an account go through this function or gaim_account_set_status or + * gaim_account_set_status_list. + * + * Only independent statuses can be deactivated with this. To deactivate + * an exclusive status, activate a different (and exclusive?) status. + * + * @param account The account. + * @param status_id The ID of the status. + * @param active The active state. + * @param args The va_list of attributes. + */ +void gaim_account_set_status_vargs(GaimAccount *account, + const char *status_id, + gboolean active, va_list args); + +/** + * Activates or deactivates a status. All changes to the statuses of + * an account go through this function or gaim_account_set_status or + * gaim_account_set_status_vargs. + * + * Only independent statuses can be deactivated with this. To deactivate + * an exclusive status, activate a different (and exclusive?) status. + * + * @param account The account. + * @param status_id The ID of the status. + * @param active The active state. + * @param attrs A list of attributes in key/value pairs + */ +void gaim_account_set_status_list(GaimAccount *account, + const char *status_id, + gboolean active, GList *attrs); + +/** + * Clears all protocol-specific settings on an account. + * + * @param account The account. + */ +void gaim_account_clear_settings(GaimAccount *account); + +/** + * Sets a protocol-specific integer setting for an account. + * + * @param account The account. + * @param name The name of the setting. + * @param value The setting's value. + */ +void gaim_account_set_int(GaimAccount *account, const char *name, int value); + +/** + * Sets a protocol-specific string setting for an account. + * + * @param account The account. + * @param name The name of the setting. + * @param value The setting's value. + */ +void gaim_account_set_string(GaimAccount *account, const char *name, + const char *value); + +/** + * Sets a protocol-specific boolean setting for an account. + * + * @param account The account. + * @param name The name of the setting. + * @param value The setting's value. + */ +void gaim_account_set_bool(GaimAccount *account, const char *name, + gboolean value); + +/** + * Sets a UI-specific integer setting for an account. + * + * @param account The account. + * @param ui The UI name. + * @param name The name of the setting. + * @param value The setting's value. + */ +void gaim_account_set_ui_int(GaimAccount *account, const char *ui, + const char *name, int value); + +/** + * Sets a UI-specific string setting for an account. + * + * @param account The account. + * @param ui The UI name. + * @param name The name of the setting. + * @param value The setting's value. + */ +void gaim_account_set_ui_string(GaimAccount *account, const char *ui, + const char *name, const char *value); + +/** + * Sets a UI-specific boolean setting for an account. + * + * @param account The account. + * @param ui The UI name. + * @param name The name of the setting. + * @param value The setting's value. + */ +void gaim_account_set_ui_bool(GaimAccount *account, const char *ui, + const char *name, gboolean value); + +/** + * Returns whether or not the account is connected. + * + * @param account The account. + * + * @return @c TRUE if connected, or @c FALSE otherwise. + */ +gboolean gaim_account_is_connected(const GaimAccount *account); + +/** + * Returns whether or not the account is connecting. + * + * @param account The account. + * + * @return @c TRUE if connecting, or @c FALSE otherwise. + */ +gboolean gaim_account_is_connecting(const GaimAccount *account); + +/** + * Returns whether or not the account is disconnected. + * + * @param account The account. + * + * @return @c TRUE if disconnected, or @c FALSE otherwise. + */ +gboolean gaim_account_is_disconnected(const GaimAccount *account); + +/** + * Returns the account's username. + * + * @param account The account. + * + * @return The username. + */ +const char *gaim_account_get_username(const GaimAccount *account); + +/** + * Returns the account's password. + * + * @param account The account. + * + * @return The password. + */ +const char *gaim_account_get_password(const GaimAccount *account); + +/** + * Returns the account's alias. + * + * @param account The account. + * + * @return The alias. + */ +const char *gaim_account_get_alias(const GaimAccount *account); + +/** + * Returns the account's user information. + * + * @param account The account. + * + * @return The user information. + */ +const char *gaim_account_get_user_info(const GaimAccount *account); + +/** + * Returns the account's buddy icon filename. + * + * @param account The account. + * + * @return The buddy icon filename. + */ +const char *gaim_account_get_buddy_icon(const GaimAccount *account); + +/** + * Returns the account's protocol ID. + * + * @param account The account. + * + * @return The protocol ID. + */ +const char *gaim_account_get_protocol_id(const GaimAccount *account); + +/** + * Returns the account's protocol name. + * + * @param account The account. + * + * @return The protocol name. + */ +const char *gaim_account_get_protocol_name(const GaimAccount *account); + +/** + * Returns the account's connection. + * + * @param account The account. + * + * @return The connection. + */ +GaimConnection *gaim_account_get_connection(const GaimAccount *account); + +/** + * Returns whether or not this account should save its password. + * + * @param account The account. + * + * @return @c TRUE if it should remember the password. + */ +gboolean gaim_account_get_remember_password(const GaimAccount *account); + +/** + * Returns whether or not this account should check for mail. + * + * @param account The account. + * + * @return @c TRUE if it should check for mail. + */ +gboolean gaim_account_get_check_mail(const GaimAccount *account); + +/** + * Returns whether or not this account is enabled for the + * specified UI. + * + * @param account The account. + * @param ui The UI. + * + * @return @c TRUE if it enabled on this UI. + */ +gboolean gaim_account_get_enabled(const GaimAccount *account, + const char *ui); + +/** + * Returns the account's proxy information. + * + * @param account The account. + * + * @return The proxy information. + */ +GaimProxyInfo *gaim_account_get_proxy_info(const GaimAccount *account); + +/** + * Returns the active status for this account. This looks through + * the GaimPresence associated with this account and returns the + * GaimStatus that has its active flag set to "TRUE." There can be + * only one active GaimStatus in a GaimPresence. + * + * @param account The account. + * + * @return The active status. + */ +GaimStatus *gaim_account_get_active_status(const GaimAccount *account); + +/** + * Returns the account status with the specified ID. + * + * Note that this works differently than gaim_buddy_get_status() in that + * it will only return NULL if the status was not registered. + * + * @param account The account. + * @param status_id The status ID. + * + * @return The status, or NULL if it was never registered. + */ +GaimStatus *gaim_account_get_status(const GaimAccount *account, + const char *status_id); + +/** + * Returns the account status type with the specified ID. + * + * @param account The account. + * @param id The ID of the status type to find. + * + * @return The status type if found, or NULL. + */ +GaimStatusType *gaim_account_get_status_type(const GaimAccount *account, + const char *id); + +/** + * Returns the account status type with the specified primitive. + * Note: It is possible for an account to have more than one + * GaimStatusType with the same primitive. In this case, the + * first GaimStatusType is returned. + * + * @param account The account. + * @param primitive The type of the status type to find. + * + * @return The status if found, or NULL. + */ +GaimStatusType *gaim_account_get_status_type_with_primitive( + const GaimAccount *account, + GaimStatusPrimitive primitive); + +/** + * Returns the account's presence. + * + * @param account The account. + * + * @return The account's presence. + */ +GaimPresence *gaim_account_get_presence(const GaimAccount *account); + +/** + * Returns whether or not an account status is active. + * + * @param account The account. + * @param status_id The status ID. + * + * @return TRUE if active, or FALSE if not. + */ +gboolean gaim_account_is_status_active(const GaimAccount *account, + const char *status_id); + +/** + * Returns the account's status types. + * + * @param account The account. + * + * @return The account's status types. + */ +const GList *gaim_account_get_status_types(const GaimAccount *account); + +/** + * Returns a protocol-specific integer setting for an account. + * + * @param account The account. + * @param name The name of the setting. + * @param default_value The default value. + * + * @return The value. + */ +int gaim_account_get_int(const GaimAccount *account, const char *name, + int default_value); + +/** + * Returns a protocol-specific string setting for an account. + * + * @param account The account. + * @param name The name of the setting. + * @param default_value The default value. + * + * @return The value. + */ +const char *gaim_account_get_string(const GaimAccount *account, + const char *name, + const char *default_value); + +/** + * Returns a protocol-specific boolean setting for an account. + * + * @param account The account. + * @param name The name of the setting. + * @param default_value The default value. + * + * @return The value. + */ +gboolean gaim_account_get_bool(const GaimAccount *account, const char *name, + gboolean default_value); + +/** + * Returns a UI-specific integer setting for an account. + * + * @param account The account. + * @param ui The UI name. + * @param name The name of the setting. + * @param default_value The default value. + * + * @return The value. + */ +int gaim_account_get_ui_int(const GaimAccount *account, const char *ui, + const char *name, int default_value); + +/** + * Returns a UI-specific string setting for an account. + * + * @param account The account. + * @param ui The UI name. + * @param name The name of the setting. + * @param default_value The default value. + * + * @return The value. + */ +const char *gaim_account_get_ui_string(const GaimAccount *account, + const char *ui, const char *name, + const char *default_value); + +/** + * Returns a UI-specific boolean setting for an account. + * + * @param account The account. + * @param ui The UI name. + * @param name The name of the setting. + * @param default_value The default value. + * + * @return The value. + */ +gboolean gaim_account_get_ui_bool(const GaimAccount *account, const char *ui, + const char *name, gboolean default_value); + + +/** + * Returns the system log for an account. + * + * @param account The account. + * @param create Should it be created if it doesn't exist? + * + * @return The log. + * + * @note Callers should almost always pass @c FALSE for @a create. + * Passing @c TRUE could result in an existing log being reopened, + * if the log has already been closed, which not all loggers deal + * with appropriately. + */ +GaimLog *gaim_account_get_log(GaimAccount *account, gboolean create); + +/** + * Frees the system log of an account + * + * @param account The account. + */ +void gaim_account_destroy_log(GaimAccount *account); + +/** + * Adds a buddy to the server-side buddy list for the specified account. + * + * @param account The account. + * @param buddy The buddy to add. + */ +void gaim_account_add_buddy(GaimAccount *account, GaimBuddy *buddy); +/** + * Adds a list of buddies to the server-side buddy list. + * + * @param account The account. + * @param buddies The list of GaimBlistNodes representing the buddies to add. + */ +void gaim_account_add_buddies(GaimAccount *account, GList *buddies); + +/** + * Removes a buddy from the server-side buddy list. + * + * @param account The account. + * @param buddy The buddy to remove. + * @param group The group to remove the buddy from. + */ +void gaim_account_remove_buddy(GaimAccount *account, GaimBuddy *buddy, + GaimGroup *group); + +/** + * Removes a list of buddies from the server-side buddy list. + * + * @note The lists buddies and groups are parallel lists. Be sure that node n of + * groups matches node n of buddies. + * + * @param account The account. + * @param buddies The list of buddies to remove. + * @param groups The list of groups to remove buddies from. Each node of this + * list should match the corresponding node of buddies. + */ +void gaim_account_remove_buddies(GaimAccount *account, GList *buddies, + GList *groups); + +/** + * Removes a group from the server-side buddy list. + * + * @param account The account. + * @param group The group to remove. + */ +void gaim_account_remove_group(GaimAccount *account, GaimGroup *group); + +/** + * Changes the password on the specified account. + * + * @param account The account. + * @param orig_pw The old password. + * @param new_pw The new password. + */ +void gaim_account_change_password(GaimAccount *account, const char *orig_pw, + const char *new_pw); + +/** + * Whether the account supports sending offline messages to buddy. + * + * @param account The account + * @param buddy The buddy + */ +gboolean gaim_account_supports_offline_message(GaimAccount *account, GaimBuddy *buddy); + +/*@}*/ + +/**************************************************************************/ +/** @name Accounts API */ +/**************************************************************************/ +/*@{*/ + +/** + * Adds an account to the list of accounts. + * + * @param account The account. + */ +void gaim_accounts_add(GaimAccount *account); + +/** + * Removes an account from the list of accounts. + * + * @param account The account. + */ +void gaim_accounts_remove(GaimAccount *account); + +/** + * Deletes an account. + * + * This will remove any buddies from the buddy list that belong to this + * account, buddy pounces that belong to this account, and will also + * destroy @a account. + * + * @param account The account. + */ +void gaim_accounts_delete(GaimAccount *account); + +/** + * Reorders an account. + * + * @param account The account to reorder. + * @param new_index The new index for the account. + */ +void gaim_accounts_reorder(GaimAccount *account, gint new_index); + +/** + * Returns a list of all accounts. + * + * @return A list of all accounts. + */ +GList *gaim_accounts_get_all(void); + +/** + * Returns a list of all enabled accounts + * + * @return A list of all enabled accounts. The list is owned + * by the caller, and must be g_list_free()d to avoid + * leaking the nodes. + */ +GList *gaim_accounts_get_all_active(void); + +/** + * Finds an account with the specified name and protocol id. + * + * @param name The account username. + * @param protocol The account protocol ID. + * + * @return The account, if found, or @c FALSE otherwise. + */ +GaimAccount *gaim_accounts_find(const char *name, const char *protocol); + +/** + * This is called by the core after all subsystems and what + * not have been initialized. It sets all enabled accounts + * to their startup status by signing them on, setting them + * away, etc. + * + * You probably shouldn't call this unless you really know + * what you're doing. + */ +void gaim_accounts_restore_current_statuses(void); + +/*@}*/ + + +/**************************************************************************/ +/** @name UI Registration Functions */ +/**************************************************************************/ +/*@{*/ +/** + * Sets the UI operations structure to be used for accounts. + * + * @param ops The UI operations structure. + */ +void gaim_accounts_set_ui_ops(GaimAccountUiOps *ops); + +/** + * Returns the UI operations structure used for accounts. + * + * @return The UI operations structure in use. + */ +GaimAccountUiOps *gaim_accounts_get_ui_ops(void); + +/*@}*/ + + +/**************************************************************************/ +/** @name Accounts Subsystem */ +/**************************************************************************/ +/*@{*/ + +/** + * Returns the accounts subsystem handle. + * + * @return The accounts subsystem handle. + */ +void *gaim_accounts_get_handle(void); + +/** + * Initializes the accounts subsystem. + */ +void gaim_accounts_init(void); + +/** + * Uninitializes the accounts subsystem. + */ +void gaim_accounts_uninit(void); + +/*@}*/ + +#ifdef __cplusplus +} +#endif + +#endif /* _GAIM_ACCOUNT_H_ */ diff -r d10dda2777a9 -r b63ebf84c42b core/accountopt.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/accountopt.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,347 @@ +/** + * @file accountopt.c Account Options API + * @ingroup core + * + * 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 "accountopt.h" +#include "util.h" + +GaimAccountOption * +gaim_account_option_new(GaimPrefType type, const char *text, + const char *pref_name) +{ + GaimAccountOption *option; + + g_return_val_if_fail(type != GAIM_PREF_NONE, NULL); + g_return_val_if_fail(text != NULL, NULL); + g_return_val_if_fail(pref_name != NULL, NULL); + + option = g_new0(GaimAccountOption, 1); + + option->type = type; + option->text = g_strdup(text); + option->pref_name = g_strdup(pref_name); + + return option; +} + +GaimAccountOption * +gaim_account_option_bool_new(const char *text, const char *pref_name, + gboolean default_value) +{ + GaimAccountOption *option; + + option = gaim_account_option_new(GAIM_PREF_BOOLEAN, text, pref_name); + + if (option == NULL) + return NULL; + + option->default_value.boolean = default_value; + + return option; +} + +GaimAccountOption * +gaim_account_option_int_new(const char *text, const char *pref_name, + int default_value) +{ + GaimAccountOption *option; + + option = gaim_account_option_new(GAIM_PREF_INT, text, pref_name); + + if (option == NULL) + return NULL; + + option->default_value.integer = default_value; + + return option; +} + +GaimAccountOption * +gaim_account_option_string_new(const char *text, const char *pref_name, + const char *default_value) +{ + GaimAccountOption *option; + + option = gaim_account_option_new(GAIM_PREF_STRING, text, pref_name); + + if (option == NULL) + return NULL; + + option->default_value.string = g_strdup(default_value); + + return option; +} + +GaimAccountOption * +gaim_account_option_list_new(const char *text, const char *pref_name, + GList *list) +{ + GaimAccountOption *option; + + option = gaim_account_option_new(GAIM_PREF_STRING_LIST, text, pref_name); + + if (option == NULL) + return NULL; + + option->default_value.list = list; + + return option; +} + +void +gaim_account_option_destroy(GaimAccountOption *option) +{ + g_return_if_fail(option != NULL); + + g_free(option->text); + g_free(option->pref_name); + + if (option->type == GAIM_PREF_STRING) + { + g_free(option->default_value.string); + } + else if (option->type == GAIM_PREF_STRING_LIST) + { + if (option->default_value.list != NULL) + { + g_list_foreach(option->default_value.list, (GFunc)g_free, NULL); + g_list_free(option->default_value.list); + } + } + + g_free(option); +} + +void +gaim_account_option_set_default_bool(GaimAccountOption *option, + gboolean value) +{ + g_return_if_fail(option != NULL); + g_return_if_fail(option->type == GAIM_PREF_BOOLEAN); + + option->default_value.boolean = value; +} + +void +gaim_account_option_set_default_int(GaimAccountOption *option, int value) +{ + g_return_if_fail(option != NULL); + g_return_if_fail(option->type == GAIM_PREF_INT); + + option->default_value.integer = value; +} + +void +gaim_account_option_set_default_string(GaimAccountOption *option, + const char *value) +{ + g_return_if_fail(option != NULL); + g_return_if_fail(option->type == GAIM_PREF_STRING); + + g_free(option->default_value.string); + option->default_value.string = g_strdup(value); +} + +void +gaim_account_option_set_masked(GaimAccountOption *option, gboolean masked) +{ + g_return_if_fail(option != NULL); + g_return_if_fail(option->type == GAIM_PREF_STRING); + + option->masked = masked; +} + + +void +gaim_account_option_set_list(GaimAccountOption *option, GList *values) +{ + g_return_if_fail(option != NULL); + g_return_if_fail(option->type == GAIM_PREF_STRING_LIST); + + if (option->default_value.list != NULL) + { + g_list_foreach(option->default_value.list, (GFunc)g_free, NULL); + g_list_free(option->default_value.list); + } + + option->default_value.list = values; +} + +void +gaim_account_option_add_list_item(GaimAccountOption *option, + const char *key, const char *value) +{ + GaimKeyValuePair *kvp; + + g_return_if_fail(option != NULL); + g_return_if_fail(key != NULL); + g_return_if_fail(value != NULL); + g_return_if_fail(option->type == GAIM_PREF_STRING_LIST); + + kvp = g_new0(GaimKeyValuePair, 1); + kvp->key = g_strdup(key); + kvp->value = g_strdup(value); + + option->default_value.list = g_list_append(option->default_value.list, + kvp); +} + +GaimPrefType +gaim_account_option_get_type(const GaimAccountOption *option) +{ + g_return_val_if_fail(option != NULL, GAIM_PREF_NONE); + + return option->type; +} + +const char * +gaim_account_option_get_text(const GaimAccountOption *option) +{ + g_return_val_if_fail(option != NULL, NULL); + + return option->text; +} + +const char * +gaim_account_option_get_setting(const GaimAccountOption *option) +{ + g_return_val_if_fail(option != NULL, NULL); + + return option->pref_name; +} + +gboolean +gaim_account_option_get_default_bool(const GaimAccountOption *option) +{ + g_return_val_if_fail(option != NULL, FALSE); + g_return_val_if_fail(option->type == GAIM_PREF_BOOLEAN, FALSE); + + return option->default_value.boolean; +} + +int +gaim_account_option_get_default_int(const GaimAccountOption *option) +{ + g_return_val_if_fail(option != NULL, -1); + g_return_val_if_fail(option->type == GAIM_PREF_INT, -1); + + return option->default_value.integer; +} + +const char * +gaim_account_option_get_default_string(const GaimAccountOption *option) +{ + g_return_val_if_fail(option != NULL, NULL); + g_return_val_if_fail(option->type == GAIM_PREF_STRING, NULL); + + return option->default_value.string; +} + +const char * +gaim_account_option_get_default_list_value(const GaimAccountOption *option) +{ + GaimKeyValuePair *kvp; + + g_return_val_if_fail(option != NULL, NULL); + g_return_val_if_fail(option->type == GAIM_PREF_STRING_LIST, NULL); + + if (option->default_value.list == NULL) + return NULL; + + kvp = option->default_value.list->data; + + return (kvp ? kvp->value : NULL); +} + +gboolean +gaim_account_option_get_masked(const GaimAccountOption *option) +{ + g_return_val_if_fail(option != NULL, FALSE); + g_return_val_if_fail(option->type == GAIM_PREF_STRING, FALSE); + + return option->masked; +} + +const GList * +gaim_account_option_get_list(const GaimAccountOption *option) +{ + g_return_val_if_fail(option != NULL, NULL); + g_return_val_if_fail(option->type == GAIM_PREF_STRING_LIST, NULL); + + return option->default_value.list; +} + +/************************************************************************** + * Account User Split API + **************************************************************************/ +GaimAccountUserSplit * +gaim_account_user_split_new(const char *text, const char *default_value, + char sep) +{ + GaimAccountUserSplit *split; + + g_return_val_if_fail(text != NULL, NULL); + g_return_val_if_fail(sep != 0, NULL); + + split = g_new0(GaimAccountUserSplit, 1); + + split->text = g_strdup(text); + split->field_sep = sep; + split->default_value = g_strdup(default_value); + + return split; +} + +void +gaim_account_user_split_destroy(GaimAccountUserSplit *split) +{ + g_return_if_fail(split != NULL); + + g_free(split->text); + g_free(split->default_value); + g_free(split); +} + +const char * +gaim_account_user_split_get_text(const GaimAccountUserSplit *split) +{ + g_return_val_if_fail(split != NULL, NULL); + + return split->text; +} + +const char * +gaim_account_user_split_get_default_value(const GaimAccountUserSplit *split) +{ + g_return_val_if_fail(split != NULL, NULL); + + return split->default_value; +} + +char +gaim_account_user_split_get_separator(const GaimAccountUserSplit *split) +{ + g_return_val_if_fail(split != NULL, 0); + + return split->field_sep; +} diff -r d10dda2777a9 -r b63ebf84c42b core/accountopt.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/accountopt.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,362 @@ +/** + * @file accountopt.h Account Options API + * @ingroup core + * + * 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 + */ +#ifndef _GAIM_ACCOUNTOPT_H_ +#define _GAIM_ACCOUNTOPT_H_ + +#include "prefs.h" + +/** + * An option for an account. + * + * This is set by protocol plugins, and appears in the account settings + * dialogs. + */ +typedef struct +{ + GaimPrefType type; /**< The type of value. */ + + char *text; /**< The text that will appear to the user. */ + char *pref_name; /**< The name of the associated preference. */ + + union + { + gboolean boolean; /**< The default boolean value. */ + int integer; /**< The default integer value. */ + char *string; /**< The default string value. */ + GList *list; /**< The default list value. */ + + } default_value; + + gboolean masked; + +} GaimAccountOption; + +/** + * A username split. + * + * This is used by some protocols to separate the fields of the username + * into more human-readable components. + */ +typedef struct +{ + char *text; /**< The text that will appear to the user. */ + char *default_value; /**< The default value. */ + char field_sep; /**< The field separator. */ + +} GaimAccountUserSplit; + +#ifdef __cplusplus +extern "C" { +#endif + +/**************************************************************************/ +/** @name Account Option API */ +/**************************************************************************/ +/*@{*/ + +/** + * Creates a new account option. + * + * @param type The type of option. + * @param text The text of the option. + * @param pref_name The account preference name for the option. + * + * @return The account option. + */ +GaimAccountOption *gaim_account_option_new(GaimPrefType type, const char *text, + const char *pref_name); + +/** + * Creates a new boolean account option. + * + * @param text The text of the option. + * @param pref_name The account preference name for the option. + * @param default_value The default value. + * + * @return The account option. + */ +GaimAccountOption *gaim_account_option_bool_new(const char *text, + const char *pref_name, + gboolean default_value); + +/** + * Creates a new integer account option. + * + * @param text The text of the option. + * @param pref_name The account preference name for the option. + * @param default_value The default value. + * + * @return The account option. + */ +GaimAccountOption *gaim_account_option_int_new(const char *text, + const char *pref_name, + int default_value); + +/** + * Creates a new string account option. + * + * @param text The text of the option. + * @param pref_name The account preference name for the option. + * @param default_value The default value. + * + * @return The account option. + */ +GaimAccountOption *gaim_account_option_string_new(const char *text, + const char *pref_name, + const char *default_value); + +/** + * Creates a new list account option. + * + * The list passed will be owned by the account option, and the + * strings inside will be freed automatically. + * + * The list is a list of GaimKeyValuePair items. The key is the ID stored and + * used internally, and the value is the label displayed. + * + * @param text The text of the option. + * @param pref_name The account preference name for the option. + * @param list The key, value list. + * + * @return The account option. + */ +GaimAccountOption *gaim_account_option_list_new(const char *text, + const char *pref_name, + GList *list); + +/** + * Destroys an account option. + * + * @param option The option to destroy. + */ +void gaim_account_option_destroy(GaimAccountOption *option); + +/** + * Sets the default boolean value for an account option. + * + * @param option The account option. + * @param value The default boolean value. + */ +void gaim_account_option_set_default_bool(GaimAccountOption *option, + gboolean value); + +/** + * Sets the default integer value for an account option. + * + * @param option The account option. + * @param value The default integer value. + */ +void gaim_account_option_set_default_int(GaimAccountOption *option, + int value); + +/** + * Sets the default string value for an account option. + * + * @param option The account option. + * @param value The default string value. + */ +void gaim_account_option_set_default_string(GaimAccountOption *option, + const char *value); + +/** + * Sets the masking for an account option. + * + * @param option The account option. + * @param masked The masking. + */ +void +gaim_account_option_set_masked(GaimAccountOption *option, gboolean masked); + +/** + * Sets the list values for an account option. + * + * The list passed will be owned by the account option, and the + * strings inside will be freed automatically. + * + * The list is in key, value pairs. The key is the ID stored and used + * internally, and the value is the label displayed. + * + * @param option The account option. + * @param values The default list value. + */ +void gaim_account_option_set_list(GaimAccountOption *option, GList *values); + +/** + * Adds an item to a list account option. + * + * @param option The account option. + * @param key The key. + * @param value The value. + */ +void gaim_account_option_add_list_item(GaimAccountOption *option, + const char *key, const char *value); + +/** + * Returns the specified account option's type. + * + * @param option The account option. + * + * @return The account option's type. + */ +GaimPrefType gaim_account_option_get_type(const GaimAccountOption *option); + +/** + * Returns the text for an account option. + * + * @param option The account option. + * + * @return The account option's text. + */ +const char *gaim_account_option_get_text(const GaimAccountOption *option); + +/** + * Returns the account setting for an account option. + * + * @param option The account option. + * + * @return The account setting. + */ +const char *gaim_account_option_get_setting(const GaimAccountOption *option); + +/** + * Returns the default boolean value for an account option. + * + * @param option The account option. + * + * @return The default boolean value. + */ +gboolean gaim_account_option_get_default_bool(const GaimAccountOption *option); + +/** + * Returns the default integer value for an account option. + * + * @param option The account option. + * + * @return The default integer value. + */ +int gaim_account_option_get_default_int(const GaimAccountOption *option); + +/** + * Returns the default string value for an account option. + * + * @param option The account option. + * + * @return The default string value. + */ +const char *gaim_account_option_get_default_string( + const GaimAccountOption *option); + +/** + * Returns the default string value for a list account option. + * + * @param option The account option. + * + * @return The default list string value. + */ +const char *gaim_account_option_get_default_list_value( + const GaimAccountOption *option); + +/** + * Returns the masking for an account option. + * + * @param option The account option. + * + * @return The masking. + */ +gboolean +gaim_account_option_get_masked(const GaimAccountOption *option); + +/** + * Returns the list values for an account option. + * + * @param option The account option. + * + * @return The list values. + */ +const GList *gaim_account_option_get_list(const GaimAccountOption *option); + +/*@}*/ + + +/**************************************************************************/ +/** @name Account User Split API */ +/**************************************************************************/ +/*@{*/ + +/** + * Creates a new account username split. + * + * @param text The text of the option. + * @param default_value The default value. + * @param sep The field separator. + * + * @return The new user split. + */ +GaimAccountUserSplit *gaim_account_user_split_new(const char *text, + const char *default_value, + char sep); + +/** + * Destroys an account username split. + * + * @param split The split to destroy. + */ +void gaim_account_user_split_destroy(GaimAccountUserSplit *split); + +/** + * Returns the text for an account username split. + * + * @param split The account username split. + * + * @return The account username split's text. + */ +const char *gaim_account_user_split_get_text(const GaimAccountUserSplit *split); + +/** + * Returns the default string value for an account split. + * + * @param split The account username split. + * + * @return The default string. + */ +const char *gaim_account_user_split_get_default_value( + const GaimAccountUserSplit *split); + +/** + * Returns the field separator for an account split. + * + * @param split The account username split. + * + * @return The field separator. + */ +char gaim_account_user_split_get_separator(const GaimAccountUserSplit *split); + +/*@}*/ + +#ifdef __cplusplus +} +#endif + +#endif /* _GAIM_ACCOUNTOPT_H_ */ diff -r d10dda2777a9 -r b63ebf84c42b core/blist.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/blist.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,2705 @@ +/* + * 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 "blist.h" +#include "conversation.h" +#include "dbus-maybe.h" +#include "debug.h" +#include "notify.h" +#include "prefs.h" +#include "privacy.h" +#include "prpl.h" +#include "server.h" +#include "signals.h" +#include "util.h" +#include "value.h" +#include "xmlnode.h" + +#define PATHSIZE 1024 + +static GaimBlistUiOps *blist_ui_ops = NULL; + +static GaimBuddyList *gaimbuddylist = NULL; +static guint save_timer = 0; +static gboolean blist_loaded = FALSE; + + +/********************************************************************* + * Private utility functions * + *********************************************************************/ + +static GaimBlistNode *gaim_blist_get_last_sibling(GaimBlistNode *node) +{ + GaimBlistNode *n = node; + if (!n) + return NULL; + while (n->next) + n = n->next; + return n; +} + +static GaimBlistNode *gaim_blist_get_last_child(GaimBlistNode *node) +{ + if (!node) + return NULL; + return gaim_blist_get_last_sibling(node->child); +} + +struct _gaim_hbuddy { + char *name; + GaimAccount *account; + GaimBlistNode *group; +}; + +static guint _gaim_blist_hbuddy_hash(struct _gaim_hbuddy *hb) +{ + return g_str_hash(hb->name); +} + +static guint _gaim_blist_hbuddy_equal(struct _gaim_hbuddy *hb1, struct _gaim_hbuddy *hb2) +{ + return ((!strcmp(hb1->name, hb2->name)) && hb1->account == hb2->account && hb1->group == hb2->group); +} + +static void _gaim_blist_hbuddy_free_key(struct _gaim_hbuddy *hb) +{ + g_free(hb->name); + g_free(hb); +} + + +/********************************************************************* + * Writing to disk * + *********************************************************************/ + +static void +value_to_xmlnode(gpointer key, gpointer hvalue, gpointer user_data) +{ + const char *name; + GaimValue *value; + xmlnode *node, *child; + char buf[20]; + + name = (const char *)key; + value = (GaimValue *)hvalue; + node = (xmlnode *)user_data; + + g_return_if_fail(value != NULL); + + child = xmlnode_new_child(node, "setting"); + xmlnode_set_attrib(child, "name", name); + + if (gaim_value_get_type(value) == GAIM_TYPE_INT) { + xmlnode_set_attrib(child, "type", "int"); + snprintf(buf, sizeof(buf), "%d", gaim_value_get_int(value)); + xmlnode_insert_data(child, buf, -1); + } + else if (gaim_value_get_type(value) == GAIM_TYPE_STRING) { + xmlnode_set_attrib(child, "type", "string"); + xmlnode_insert_data(child, gaim_value_get_string(value), -1); + } + else if (gaim_value_get_type(value) == GAIM_TYPE_BOOLEAN) { + xmlnode_set_attrib(child, "type", "bool"); + snprintf(buf, sizeof(buf), "%d", gaim_value_get_boolean(value)); + xmlnode_insert_data(child, buf, -1); + } +} + +static void +chat_component_to_xmlnode(gpointer key, gpointer value, gpointer user_data) +{ + const char *name; + const char *data; + xmlnode *node, *child; + + name = (const char *)key; + data = (const char *)value; + node = (xmlnode *)user_data; + + g_return_if_fail(data != NULL); + + child = xmlnode_new_child(node, "component"); + xmlnode_set_attrib(child, "name", name); + xmlnode_insert_data(child, data, -1); +} + +static xmlnode * +buddy_to_xmlnode(GaimBlistNode *bnode) +{ + xmlnode *node, *child; + GaimBuddy *buddy; + + buddy = (GaimBuddy *)bnode; + + node = xmlnode_new("buddy"); + xmlnode_set_attrib(node, "account", gaim_account_get_username(buddy->account)); + xmlnode_set_attrib(node, "proto", gaim_account_get_protocol_id(buddy->account)); + + child = xmlnode_new_child(node, "name"); + xmlnode_insert_data(child, buddy->name, -1); + + if (buddy->alias != NULL) + { + child = xmlnode_new_child(node, "alias"); + xmlnode_insert_data(child, buddy->alias, -1); + } + + /* Write buddy settings */ + g_hash_table_foreach(buddy->node.settings, value_to_xmlnode, node); + + return node; +} + +static xmlnode * +contact_to_xmlnode(GaimBlistNode *cnode) +{ + xmlnode *node, *child; + GaimContact *contact; + GaimBlistNode *bnode; + + contact = (GaimContact *)cnode; + + node = xmlnode_new("contact"); + + if (contact->alias != NULL) + { + xmlnode_set_attrib(node, "alias", contact->alias); + } + + /* Write buddies */ + for (bnode = cnode->child; bnode != NULL; bnode = bnode->next) + { + if (!GAIM_BLIST_NODE_SHOULD_SAVE(bnode)) + continue; + if (GAIM_BLIST_NODE_IS_BUDDY(bnode)) + { + child = buddy_to_xmlnode(bnode); + xmlnode_insert_child(node, child); + } + } + + /* Write contact settings */ + g_hash_table_foreach(cnode->settings, value_to_xmlnode, node); + + return node; +} + +static xmlnode * +chat_to_xmlnode(GaimBlistNode *cnode) +{ + xmlnode *node, *child; + GaimChat *chat; + + chat = (GaimChat *)cnode; + + node = xmlnode_new("chat"); + xmlnode_set_attrib(node, "proto", gaim_account_get_protocol_id(chat->account)); + xmlnode_set_attrib(node, "account", gaim_account_get_username(chat->account)); + + if (chat->alias != NULL) + { + child = xmlnode_new_child(node, "alias"); + xmlnode_insert_data(child, chat->alias, -1); + } + + /* Write chat components */ + g_hash_table_foreach(chat->components, chat_component_to_xmlnode, node); + + /* Write chat settings */ + g_hash_table_foreach(chat->node.settings, value_to_xmlnode, node); + + return node; +} + +static xmlnode * +group_to_xmlnode(GaimBlistNode *gnode) +{ + xmlnode *node, *child; + GaimGroup *group; + GaimBlistNode *cnode; + + group = (GaimGroup *)gnode; + + node = xmlnode_new("group"); + xmlnode_set_attrib(node, "name", group->name); + + /* Write settings */ + g_hash_table_foreach(group->node.settings, value_to_xmlnode, node); + + /* Write contacts and chats */ + for (cnode = gnode->child; cnode != NULL; cnode = cnode->next) + { + if (!GAIM_BLIST_NODE_SHOULD_SAVE(cnode)) + continue; + if (GAIM_BLIST_NODE_IS_CONTACT(cnode)) + { + child = contact_to_xmlnode(cnode); + xmlnode_insert_child(node, child); + } + else if (GAIM_BLIST_NODE_IS_CHAT(cnode)) + { + child = chat_to_xmlnode(cnode); + xmlnode_insert_child(node, child); + } + } + + return node; +} + +static xmlnode * +accountprivacy_to_xmlnode(GaimAccount *account) +{ + xmlnode *node, *child; + GSList *cur; + char buf[10]; + + node = xmlnode_new("account"); + xmlnode_set_attrib(node, "proto", gaim_account_get_protocol_id(account)); + xmlnode_set_attrib(node, "name", gaim_account_get_username(account)); + snprintf(buf, sizeof(buf), "%d", account->perm_deny); + xmlnode_set_attrib(node, "mode", buf); + + for (cur = account->permit; cur; cur = cur->next) + { + child = xmlnode_new_child(node, "permit"); + xmlnode_insert_data(child, cur->data, -1); + } + + for (cur = account->deny; cur; cur = cur->next) + { + child = xmlnode_new_child(node, "block"); + xmlnode_insert_data(child, cur->data, -1); + } + + return node; +} + +static xmlnode * +blist_to_xmlnode() +{ + xmlnode *node, *child, *grandchild; + GaimBlistNode *gnode; + GList *cur; + + node = xmlnode_new("gaim"); + xmlnode_set_attrib(node, "version", "1.0"); + + /* Write groups */ + child = xmlnode_new_child(node, "blist"); + for (gnode = gaimbuddylist->root; gnode != NULL; gnode = gnode->next) + { + if (!GAIM_BLIST_NODE_SHOULD_SAVE(gnode)) + continue; + if (GAIM_BLIST_NODE_IS_GROUP(gnode)) + { + grandchild = group_to_xmlnode(gnode); + xmlnode_insert_child(child, grandchild); + } + } + + /* Write privacy settings */ + child = xmlnode_new_child(node, "privacy"); + for (cur = gaim_accounts_get_all(); cur != NULL; cur = cur->next) + { + grandchild = accountprivacy_to_xmlnode(cur->data); + xmlnode_insert_child(child, grandchild); + } + + return node; +} + +static void +gaim_blist_sync() +{ + xmlnode *node; + char *data; + + if (!blist_loaded) + { + gaim_debug_error("blist", "Attempted to save buddy list before it " + "was read!\n"); + return; + } + + node = blist_to_xmlnode(); + data = xmlnode_to_formatted_str(node, NULL); + gaim_util_write_data_to_file("blist.xml", data, -1); + g_free(data); + xmlnode_free(node); +} + +static gboolean +save_cb(gpointer data) +{ + gaim_blist_sync(); + save_timer = 0; + return FALSE; +} + +void +gaim_blist_schedule_save() +{ + if (save_timer == 0) + save_timer = gaim_timeout_add(5000, save_cb, NULL); +} + + +/********************************************************************* + * Reading from disk * + *********************************************************************/ + +static void +parse_setting(GaimBlistNode *node, xmlnode *setting) +{ + const char *name = xmlnode_get_attrib(setting, "name"); + const char *type = xmlnode_get_attrib(setting, "type"); + char *value = xmlnode_get_data(setting); + + if (!value) + return; + + if (!type || !strcmp(type, "string")) + gaim_blist_node_set_string(node, name, value); + else if (!strcmp(type, "bool")) + gaim_blist_node_set_bool(node, name, atoi(value)); + else if (!strcmp(type, "int")) + gaim_blist_node_set_int(node, name, atoi(value)); + + g_free(value); +} + +static void +parse_buddy(GaimGroup *group, GaimContact *contact, xmlnode *bnode) +{ + GaimAccount *account; + GaimBuddy *buddy; + char *name = NULL, *alias = NULL; + const char *acct_name, *proto, *protocol; + xmlnode *x; + + acct_name = xmlnode_get_attrib(bnode, "account"); + protocol = xmlnode_get_attrib(bnode, "protocol"); + proto = xmlnode_get_attrib(bnode, "proto"); + + if (!acct_name || (!proto && !protocol)) + return; + + account = gaim_accounts_find(acct_name, proto ? proto : protocol); + + if (!account) + return; + + if ((x = xmlnode_get_child(bnode, "name"))) + name = xmlnode_get_data(x); + + if (!name) + return; + + if ((x = xmlnode_get_child(bnode, "alias"))) + alias = xmlnode_get_data(x); + + buddy = gaim_buddy_new(account, name, alias); + gaim_blist_add_buddy(buddy, contact, group, + gaim_blist_get_last_child((GaimBlistNode*)contact)); + + for (x = xmlnode_get_child(bnode, "setting"); x; x = xmlnode_get_next_twin(x)) { + parse_setting((GaimBlistNode*)buddy, x); + } + + g_free(name); + g_free(alias); +} + +static void +parse_contact(GaimGroup *group, xmlnode *cnode) +{ + GaimContact *contact = gaim_contact_new(); + xmlnode *x; + const char *alias; + + gaim_blist_add_contact(contact, group, + gaim_blist_get_last_child((GaimBlistNode*)group)); + + if ((alias = xmlnode_get_attrib(cnode, "alias"))) { + gaim_contact_set_alias(contact, alias); + } + + for (x = cnode->child; x; x = x->next) { + if (x->type != XMLNODE_TYPE_TAG) + continue; + if (!strcmp(x->name, "buddy")) + parse_buddy(group, contact, x); + else if (!strcmp(x->name, "setting")) + parse_setting((GaimBlistNode*)contact, x); + } + + /* if the contact is empty, don't keep it around. it causes problems */ + if (!((GaimBlistNode*)contact)->child) + gaim_blist_remove_contact(contact); +} + +static void +parse_chat(GaimGroup *group, xmlnode *cnode) +{ + GaimChat *chat; + GaimAccount *account; + const char *acct_name, *proto, *protocol; + xmlnode *x; + char *alias = NULL; + GHashTable *components; + + acct_name = xmlnode_get_attrib(cnode, "account"); + protocol = xmlnode_get_attrib(cnode, "protocol"); + proto = xmlnode_get_attrib(cnode, "proto"); + + if (!acct_name || (!proto && !protocol)) + return; + + account = gaim_accounts_find(acct_name, proto ? proto : protocol); + + if (!account) + return; + + if ((x = xmlnode_get_child(cnode, "alias"))) + alias = xmlnode_get_data(x); + + components = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + + for (x = xmlnode_get_child(cnode, "component"); x; x = xmlnode_get_next_twin(x)) { + const char *name; + char *value; + + name = xmlnode_get_attrib(x, "name"); + value = xmlnode_get_data(x); + g_hash_table_replace(components, g_strdup(name), value); + } + + chat = gaim_chat_new(account, alias, components); + gaim_blist_add_chat(chat, group, + gaim_blist_get_last_child((GaimBlistNode*)group)); + + for (x = xmlnode_get_child(cnode, "setting"); x; x = xmlnode_get_next_twin(x)) { + parse_setting((GaimBlistNode*)chat, x); + } + + g_free(alias); +} + +static void +parse_group(xmlnode *groupnode) +{ + const char *name = xmlnode_get_attrib(groupnode, "name"); + GaimGroup *group; + xmlnode *cnode; + + if (!name) + name = _("Buddies"); + + group = gaim_group_new(name); + gaim_blist_add_group(group, + gaim_blist_get_last_sibling(gaimbuddylist->root)); + + for (cnode = groupnode->child; cnode; cnode = cnode->next) { + if (cnode->type != XMLNODE_TYPE_TAG) + continue; + if (!strcmp(cnode->name, "setting")) + parse_setting((GaimBlistNode*)group, cnode); + else if (!strcmp(cnode->name, "contact") || + !strcmp(cnode->name, "person")) + parse_contact(group, cnode); + else if (!strcmp(cnode->name, "chat")) + parse_chat(group, cnode); + } +} + +/* TODO: Make static and rename to load_blist */ +void +gaim_blist_load() +{ + xmlnode *gaim, *blist, *privacy; + + blist_loaded = TRUE; + + gaim = gaim_util_read_xml_from_file("blist.xml", _("buddy list")); + + if (gaim == NULL) + return; + + blist = xmlnode_get_child(gaim, "blist"); + if (blist) { + xmlnode *groupnode; + for (groupnode = xmlnode_get_child(blist, "group"); groupnode != NULL; + groupnode = xmlnode_get_next_twin(groupnode)) { + parse_group(groupnode); + } + } + + privacy = xmlnode_get_child(gaim, "privacy"); + if (privacy) { + xmlnode *anode; + for (anode = privacy->child; anode; anode = anode->next) { + xmlnode *x; + GaimAccount *account; + int imode; + const char *acct_name, *proto, *mode, *protocol; + + acct_name = xmlnode_get_attrib(anode, "name"); + protocol = xmlnode_get_attrib(anode, "protocol"); + proto = xmlnode_get_attrib(anode, "proto"); + mode = xmlnode_get_attrib(anode, "mode"); + + if (!acct_name || (!proto && !protocol) || !mode) + continue; + + account = gaim_accounts_find(acct_name, proto ? proto : protocol); + + if (!account) + continue; + + imode = atoi(mode); + account->perm_deny = (imode != 0 ? imode : GAIM_PRIVACY_ALLOW_ALL); + + for (x = anode->child; x; x = x->next) { + char *name; + if (x->type != XMLNODE_TYPE_TAG) + continue; + + if (!strcmp(x->name, "permit")) { + name = xmlnode_get_data(x); + gaim_privacy_permit_add(account, name, TRUE); + g_free(name); + } else if (!strcmp(x->name, "block")) { + name = xmlnode_get_data(x); + gaim_privacy_deny_add(account, name, TRUE); + g_free(name); + } + } + } + } + + xmlnode_free(gaim); +} + + +/********************************************************************* + * Stuff * + *********************************************************************/ + +static void +gaim_contact_compute_priority_buddy(GaimContact *contact) +{ + GaimBlistNode *bnode; + GaimBuddy *new_priority = NULL; + + g_return_if_fail(contact != NULL); + + contact->priority = NULL; + for (bnode = ((GaimBlistNode*)contact)->child; + bnode != NULL; + bnode = bnode->next) + { + GaimBuddy *buddy; + + if (!GAIM_BLIST_NODE_IS_BUDDY(bnode)) + continue; + + buddy = (GaimBuddy*)bnode; + + if (!gaim_account_is_connected(buddy->account)) + continue; + if (new_priority == NULL) + new_priority = buddy; + else + { + int cmp; + + cmp = gaim_presence_compare(gaim_buddy_get_presence(new_priority), + gaim_buddy_get_presence(buddy)); + + if (cmp > 0 || (cmp == 0 && + gaim_prefs_get_bool("/core/contact/last_match"))) + { + new_priority = buddy; + } + } + } + + contact->priority = new_priority; + contact->priority_valid = TRUE; +} + + +/***************************************************************************** + * Public API functions * + *****************************************************************************/ + +GaimBuddyList *gaim_blist_new() +{ + GaimBlistUiOps *ui_ops; + GaimBuddyList *gbl = g_new0(GaimBuddyList, 1); + GAIM_DBUS_REGISTER_POINTER(gbl, GaimBuddyList); + + ui_ops = gaim_blist_get_ui_ops(); + + gbl->buddies = g_hash_table_new_full((GHashFunc)_gaim_blist_hbuddy_hash, + (GEqualFunc)_gaim_blist_hbuddy_equal, + (GDestroyNotify)_gaim_blist_hbuddy_free_key, NULL); + + if (ui_ops != NULL && ui_ops->new_list != NULL) + ui_ops->new_list(gbl); + + return gbl; +} + +void +gaim_set_blist(GaimBuddyList *list) +{ + gaimbuddylist = list; +} + +GaimBuddyList * +gaim_get_blist() +{ + return gaimbuddylist; +} + +GaimBlistNode * +gaim_blist_get_root() +{ + return gaimbuddylist ? gaimbuddylist->root : NULL; +} + +void gaim_blist_show() +{ + GaimBlistUiOps *ops = gaim_blist_get_ui_ops(); + + if (ops && ops->show) + ops->show(gaimbuddylist); +} + +void gaim_blist_destroy() +{ + GaimBlistUiOps *ops = gaim_blist_get_ui_ops(); + + gaim_debug(GAIM_DEBUG_INFO, "blist", "Destroying\n"); + + if (ops && ops->destroy) + ops->destroy(gaimbuddylist); +} + +void gaim_blist_set_visible(gboolean show) +{ + GaimBlistUiOps *ops = gaim_blist_get_ui_ops(); + + if (ops && ops->set_visible) + ops->set_visible(gaimbuddylist, show); +} + +static GaimBlistNode *get_next_node(GaimBlistNode *node, gboolean godeep) +{ + if (node == NULL) + return NULL; + + if (godeep && node->child) + return node->child; + + if (node->next) + return node->next; + + return get_next_node(node->parent, FALSE); +} + +GaimBlistNode *gaim_blist_node_next(GaimBlistNode *node, gboolean offline) +{ + GaimBlistNode *ret = node; + + if (offline) + return get_next_node(ret, TRUE); + do + { + ret = get_next_node(ret, TRUE); + } while (ret && GAIM_BLIST_NODE_IS_BUDDY(ret) && + !gaim_account_is_connected(gaim_buddy_get_account((GaimBuddy *)ret))); + + return ret; +} + +void +gaim_blist_update_buddy_status(GaimBuddy *buddy, GaimStatus *old_status) +{ + GaimBlistUiOps *ops = gaim_blist_get_ui_ops(); + GaimPresence *presence; + GaimStatus *status; + + g_return_if_fail(buddy != NULL); + + presence = gaim_buddy_get_presence(buddy); + status = gaim_presence_get_active_status(presence); + + gaim_debug_info("blist", "Updating buddy status for %s (%s)\n", + buddy->name, gaim_account_get_protocol_name(buddy->account)); + + if (gaim_status_is_online(status) && + !gaim_status_is_online(old_status)) { + + gaim_signal_emit(gaim_blist_get_handle(), "buddy-signed-on", buddy); + + ((GaimContact*)((GaimBlistNode*)buddy)->parent)->online++; + if (((GaimContact*)((GaimBlistNode*)buddy)->parent)->online == 1) + ((GaimGroup *)((GaimBlistNode *)buddy)->parent->parent)->online++; + } else if (!gaim_status_is_online(status) && + gaim_status_is_online(old_status)) { + gaim_blist_node_set_int(&buddy->node, "last_seen", time(NULL)); + gaim_signal_emit(gaim_blist_get_handle(), "buddy-signed-off", buddy); + ((GaimContact*)((GaimBlistNode*)buddy)->parent)->online--; + if (((GaimContact*)((GaimBlistNode*)buddy)->parent)->online == 0) + ((GaimGroup *)((GaimBlistNode *)buddy)->parent->parent)->online--; + } else { + gaim_signal_emit(gaim_blist_get_handle(), + "buddy-status-changed", buddy, old_status, + status); + } + + /* + * This function used to only call the following two functions if one of + * the above signals had been triggered, but that's not good, because + * if someone's away message changes and they don't go from away to back + * to away then no signal is triggered. + * + * It's a safe assumption that SOMETHING called this function. PROBABLY + * because something, somewhere changed. Calling the stuff below + * certainly won't hurt anything. Unless you're on a K6-2 300. + */ + gaim_contact_invalidate_priority_buddy(gaim_buddy_get_contact(buddy)); + if (ops && ops->update) + ops->update(gaimbuddylist, (GaimBlistNode *)buddy); +} + +void gaim_blist_update_buddy_icon(GaimBuddy *buddy) +{ + GaimBlistUiOps *ops = gaim_blist_get_ui_ops(); + + g_return_if_fail(buddy != NULL); + + if (ops && ops->update) + ops->update(gaimbuddylist, (GaimBlistNode *)buddy); +} + +/* + * TODO: Maybe remove the call to this from server.c and call it + * from oscar.c and toc.c instead? + */ +void gaim_blist_rename_buddy(GaimBuddy *buddy, const char *name) +{ + GaimBlistUiOps *ops = gaim_blist_get_ui_ops(); + struct _gaim_hbuddy *hb; + + g_return_if_fail(buddy != NULL); + + hb = g_new(struct _gaim_hbuddy, 1); + hb->name = g_strdup(gaim_normalize(buddy->account, buddy->name)); + hb->account = buddy->account; + hb->group = ((GaimBlistNode *)buddy)->parent->parent; + g_hash_table_remove(gaimbuddylist->buddies, hb); + + g_free(hb->name); + hb->name = g_strdup(gaim_normalize(buddy->account, name)); + g_hash_table_replace(gaimbuddylist->buddies, hb, buddy); + + g_free(buddy->name); + buddy->name = g_strdup(name); + + gaim_blist_schedule_save(); + + if (ops && ops->update) + ops->update(gaimbuddylist, (GaimBlistNode *)buddy); +} + +void gaim_blist_alias_contact(GaimContact *contact, const char *alias) +{ + GaimBlistUiOps *ops = gaim_blist_get_ui_ops(); + GaimConversation *conv; + char *old_alias = contact->alias; + GaimBlistNode *bnode; + + g_return_if_fail(contact != NULL); + + if ((alias != NULL) && (*alias != '\0')) + contact->alias = g_strdup(alias); + else + contact->alias = NULL; + + gaim_blist_schedule_save(); + + if (ops && ops->update) + ops->update(gaimbuddylist, (GaimBlistNode *)contact); + + for(bnode = ((GaimBlistNode *)contact)->child; bnode != NULL; bnode = bnode->next) + { + GaimBuddy *buddy = (GaimBuddy *)bnode; + + conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM, buddy->name, + buddy->account); + if (conv) + gaim_conversation_autoset_title(conv); + } + + gaim_signal_emit(gaim_blist_get_handle(), "blist-node-aliased", + contact, old_alias); + g_free(old_alias); +} + +void gaim_blist_alias_chat(GaimChat *chat, const char *alias) +{ + GaimBlistUiOps *ops = gaim_blist_get_ui_ops(); + char *old_alias = chat->alias; + + g_return_if_fail(chat != NULL); + + if ((alias != NULL) && (*alias != '\0')) + chat->alias = g_strdup(alias); + else + chat->alias = NULL; + + gaim_blist_schedule_save(); + + if (ops && ops->update) + ops->update(gaimbuddylist, (GaimBlistNode *)chat); + + gaim_signal_emit(gaim_blist_get_handle(), "blist-node-aliased", + chat, old_alias); + g_free(old_alias); +} + +void gaim_blist_alias_buddy(GaimBuddy *buddy, const char *alias) +{ + GaimBlistUiOps *ops = gaim_blist_get_ui_ops(); + GaimConversation *conv; + char *old_alias = buddy->alias; + + g_return_if_fail(buddy != NULL); + + if ((alias != NULL) && (*alias != '\0')) + buddy->alias = g_strdup(alias); + else + buddy->alias = NULL; + + gaim_blist_schedule_save(); + + if (ops && ops->update) + ops->update(gaimbuddylist, (GaimBlistNode *)buddy); + + conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM, buddy->name, + buddy->account); + if (conv) + gaim_conversation_autoset_title(conv); + + gaim_signal_emit(gaim_blist_get_handle(), "blist-node-aliased", + buddy, old_alias); + g_free(old_alias); +} + +void gaim_blist_server_alias_buddy(GaimBuddy *buddy, const char *alias) +{ + GaimBlistUiOps *ops = gaim_blist_get_ui_ops(); + GaimConversation *conv; + char *old_alias = buddy->server_alias; + + g_return_if_fail(buddy != NULL); + + if ((alias != NULL) && (*alias != '\0') && g_utf8_validate(alias, -1, NULL)) + buddy->server_alias = g_strdup(alias); + else + buddy->server_alias = NULL; + + gaim_blist_schedule_save(); + + if (ops && ops->update) + ops->update(gaimbuddylist, (GaimBlistNode *)buddy); + + conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM, buddy->name, + buddy->account); + if (conv) + gaim_conversation_autoset_title(conv); + + gaim_signal_emit(gaim_blist_get_handle(), "blist-node-aliased", + buddy, old_alias); + g_free(old_alias); +} + +/* + * TODO: If merging, prompt the user if they want to merge. + */ +void gaim_blist_rename_group(GaimGroup *source, const char *new_name) +{ + GaimBlistUiOps *ops = gaim_blist_get_ui_ops(); + GaimGroup *dest; + gchar *old_name; + GList *moved_buddies = NULL; + GSList *accts; + + g_return_if_fail(source != NULL); + g_return_if_fail(new_name != NULL); + + if (*new_name == '\0' || !strcmp(new_name, source->name)) + return; + + dest = gaim_find_group(new_name); + if (dest != NULL) { + /* We're merging two groups */ + GaimBlistNode *prev, *child, *next; + + prev = gaim_blist_get_last_child((GaimBlistNode*)dest); + child = ((GaimBlistNode*)source)->child; + + /* + * TODO: This seems like a dumb way to do this... why not just + * append all children from the old group to the end of the new + * one? PRPLs might be expecting to receive an add_buddy() for + * each moved buddy... + */ + while (child) + { + next = child->next; + if (GAIM_BLIST_NODE_IS_CONTACT(child)) { + GaimBlistNode *bnode; + gaim_blist_add_contact((GaimContact *)child, dest, prev); + for (bnode = child->child; bnode != NULL; bnode = bnode->next) { + gaim_blist_add_buddy((GaimBuddy *)bnode, (GaimContact *)child, + NULL, bnode->prev); + moved_buddies = g_list_append(moved_buddies, bnode); + } + prev = child; + } else if (GAIM_BLIST_NODE_IS_CHAT(child)) { + gaim_blist_add_chat((GaimChat *)child, dest, prev); + prev = child; + } else { + gaim_debug(GAIM_DEBUG_ERROR, "blist", + "Unknown child type in group %s\n", source->name); + } + child = next; + } + + /* Make a copy of the old group name and then delete the old group */ + old_name = g_strdup(source->name); + gaim_blist_remove_group(source); + source = dest; + } else { + /* A simple rename */ + GaimBlistNode *cnode, *bnode; + + /* Build a GList of all buddies in this group */ + for (cnode = ((GaimBlistNode *)source)->child; cnode != NULL; cnode = cnode->next) { + if (GAIM_BLIST_NODE_IS_CONTACT(cnode)) + for (bnode = cnode->child; bnode != NULL; bnode = bnode->next) + moved_buddies = g_list_append(moved_buddies, bnode); + } + + old_name = source->name; + source->name = g_strdup(new_name); + } + + /* Save our changes */ + gaim_blist_schedule_save(); + + /* Update the UI */ + if (ops && ops->update) + ops->update(gaimbuddylist, (GaimBlistNode*)source); + + /* Notify all PRPLs */ + /* TODO: Is this condition needed? Seems like it would always be TRUE */ + if(old_name && source && strcmp(source->name, old_name)) { + for (accts = gaim_group_get_accounts(source); accts; accts = g_slist_remove(accts, accts->data)) { + GaimAccount *account = accts->data; + GaimPluginProtocolInfo *prpl_info = NULL; + GList *l = NULL, *buddies = NULL; + + if(account->gc && account->gc->prpl) + prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(account->gc->prpl); + + if(!prpl_info) + continue; + + for(l = moved_buddies; l; l = l->next) { + GaimBuddy *buddy = (GaimBuddy *)l->data; + + if(buddy && buddy->account == account) + buddies = g_list_append(buddies, (GaimBlistNode *)buddy); + } + + if(prpl_info->rename_group) { + prpl_info->rename_group(account->gc, old_name, source, buddies); + } else { + GList *cur, *groups = NULL; + + /* Make a list of what the groups each buddy is in */ + for(cur = buddies; cur; cur = cur->next) { + GaimBlistNode *node = (GaimBlistNode *)cur->data; + groups = g_list_prepend(groups, node->parent->parent); + } + + gaim_account_remove_buddies(account, buddies, groups); + g_list_free(groups); + gaim_account_add_buddies(account, buddies); + } + + g_list_free(buddies); + } + } + g_list_free(moved_buddies); + g_free(old_name); +} + +static void gaim_blist_node_initialize_settings(GaimBlistNode *node); + +GaimChat *gaim_chat_new(GaimAccount *account, const char *alias, GHashTable *components) +{ + GaimBlistUiOps *ops = gaim_blist_get_ui_ops(); + GaimChat *chat; + + g_return_val_if_fail(account != NULL, FALSE); + g_return_val_if_fail(components != NULL, FALSE); + + chat = g_new0(GaimChat, 1); + chat->account = account; + if ((alias != NULL) && (*alias != '\0')) + chat->alias = g_strdup(alias); + chat->components = components; + gaim_blist_node_initialize_settings((GaimBlistNode *)chat); + ((GaimBlistNode *)chat)->type = GAIM_BLIST_CHAT_NODE; + + if (ops != NULL && ops->new_node != NULL) + ops->new_node((GaimBlistNode *)chat); + + GAIM_DBUS_REGISTER_POINTER(chat, GaimChat); + return chat; +} + +GaimBuddy *gaim_buddy_new(GaimAccount *account, const char *screenname, const char *alias) +{ + GaimBlistUiOps *ops = gaim_blist_get_ui_ops(); + GaimBuddy *buddy; + + g_return_val_if_fail(account != NULL, FALSE); + g_return_val_if_fail(screenname != NULL, FALSE); + + buddy = g_new0(GaimBuddy, 1); + buddy->account = account; + buddy->name = g_strdup(screenname); + buddy->alias = g_strdup(alias); + buddy->presence = gaim_presence_new_for_buddy(buddy); + ((GaimBlistNode *)buddy)->type = GAIM_BLIST_BUDDY_NODE; + + gaim_presence_set_status_active(buddy->presence, "offline", TRUE); + + gaim_blist_node_initialize_settings((GaimBlistNode *)buddy); + + if (ops && ops->new_node) + ops->new_node((GaimBlistNode *)buddy); + + GAIM_DBUS_REGISTER_POINTER(buddy, GaimBuddy); + return buddy; +} + +void +gaim_buddy_set_icon(GaimBuddy *buddy, GaimBuddyIcon *icon) +{ + g_return_if_fail(buddy != NULL); + + if (buddy->icon != icon) { + if (buddy->icon != NULL) + gaim_buddy_icon_unref(buddy->icon); + + buddy->icon = (icon != NULL ? gaim_buddy_icon_ref(icon) : NULL); + } + + if (buddy->icon) + gaim_buddy_icon_cache(icon, buddy); + else + gaim_buddy_icon_uncache(buddy); + + gaim_blist_schedule_save(); + + gaim_signal_emit(gaim_blist_get_handle(), "buddy-icon-changed", buddy); + + gaim_blist_update_buddy_icon(buddy); +} + +GaimAccount * +gaim_buddy_get_account(const GaimBuddy *buddy) +{ + g_return_val_if_fail(buddy != NULL, NULL); + + return buddy->account; +} + +const char * +gaim_buddy_get_name(const GaimBuddy *buddy) +{ + g_return_val_if_fail(buddy != NULL, NULL); + + return buddy->name; +} + +GaimBuddyIcon * +gaim_buddy_get_icon(const GaimBuddy *buddy) +{ + g_return_val_if_fail(buddy != NULL, NULL); + + return buddy->icon; +} + +void gaim_blist_add_chat(GaimChat *chat, GaimGroup *group, GaimBlistNode *node) +{ + GaimBlistNode *cnode = (GaimBlistNode*)chat; + GaimBlistUiOps *ops = gaim_blist_get_ui_ops(); + + g_return_if_fail(chat != NULL); + g_return_if_fail(GAIM_BLIST_NODE_IS_CHAT((GaimBlistNode *)chat)); + + if (node == NULL) { + if (group == NULL) { + group = gaim_group_new(_("Chats")); + gaim_blist_add_group(group, + gaim_blist_get_last_sibling(gaimbuddylist->root)); + } + } else { + group = (GaimGroup*)node->parent; + } + + /* if we're moving to overtop of ourselves, do nothing */ + if (cnode == node) + return; + + if (cnode->parent) { + /* This chat was already in the list and is + * being moved. + */ + ((GaimGroup *)cnode->parent)->totalsize--; + if (gaim_account_is_connected(chat->account)) { + ((GaimGroup *)cnode->parent)->online--; + ((GaimGroup *)cnode->parent)->currentsize--; + } + if (cnode->next) + cnode->next->prev = cnode->prev; + if (cnode->prev) + cnode->prev->next = cnode->next; + if (cnode->parent->child == cnode) + cnode->parent->child = cnode->next; + + if (ops && ops->remove) + ops->remove(gaimbuddylist, cnode); + /* ops->remove() cleaned up the cnode's ui_data, so we need to + * reinitialize it */ + if (ops && ops->new_node) + ops->new_node(cnode); + + gaim_blist_schedule_save(); + } + + if (node != NULL) { + if (node->next) + node->next->prev = cnode; + cnode->next = node->next; + cnode->prev = node; + cnode->parent = node->parent; + node->next = cnode; + ((GaimGroup *)node->parent)->totalsize++; + if (gaim_account_is_connected(chat->account)) { + ((GaimGroup *)node->parent)->online++; + ((GaimGroup *)node->parent)->currentsize++; + } + } else { + if (((GaimBlistNode *)group)->child) + ((GaimBlistNode *)group)->child->prev = cnode; + cnode->next = ((GaimBlistNode *)group)->child; + cnode->prev = NULL; + ((GaimBlistNode *)group)->child = cnode; + cnode->parent = (GaimBlistNode *)group; + group->totalsize++; + if (gaim_account_is_connected(chat->account)) { + group->online++; + group->currentsize++; + } + } + + gaim_blist_schedule_save(); + + if (ops && ops->update) + ops->update(gaimbuddylist, (GaimBlistNode *)cnode); +} + +void gaim_blist_add_buddy(GaimBuddy *buddy, GaimContact *contact, GaimGroup *group, GaimBlistNode *node) +{ + GaimBlistNode *cnode, *bnode; + GaimGroup *g; + GaimContact *c; + GaimBlistUiOps *ops = gaim_blist_get_ui_ops(); + struct _gaim_hbuddy *hb; + + g_return_if_fail(buddy != NULL); + g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY((GaimBlistNode*)buddy)); + + bnode = (GaimBlistNode *)buddy; + + /* if we're moving to overtop of ourselves, do nothing */ + if (bnode == node || (!node && bnode->parent && + contact && bnode->parent == (GaimBlistNode*)contact + && bnode == bnode->parent->child)) + return; + + if (node && GAIM_BLIST_NODE_IS_BUDDY(node)) { + c = (GaimContact*)node->parent; + g = (GaimGroup*)node->parent->parent; + } else if (contact) { + c = contact; + g = (GaimGroup *)((GaimBlistNode *)c)->parent; + } else { + if (group) { + g = group; + } else { + g = gaim_group_new(_("Buddies")); + gaim_blist_add_group(g, + gaim_blist_get_last_sibling(gaimbuddylist->root)); + } + c = gaim_contact_new(); + gaim_blist_add_contact(c, g, + gaim_blist_get_last_child((GaimBlistNode*)g)); + } + + cnode = (GaimBlistNode *)c; + + if (bnode->parent) { + if (GAIM_BUDDY_IS_ONLINE(buddy)) { + ((GaimContact*)bnode->parent)->online--; + if (((GaimContact*)bnode->parent)->online == 0) + ((GaimGroup*)bnode->parent->parent)->online--; + } + if (gaim_account_is_connected(buddy->account)) { + ((GaimContact*)bnode->parent)->currentsize--; + if (((GaimContact*)bnode->parent)->currentsize == 0) + ((GaimGroup*)bnode->parent->parent)->currentsize--; + } + ((GaimContact*)bnode->parent)->totalsize--; + /* the group totalsize will be taken care of by remove_contact below */ + + if (bnode->parent->parent != (GaimBlistNode*)g) + serv_move_buddy(buddy, (GaimGroup *)bnode->parent->parent, g); + + if (bnode->next) + bnode->next->prev = bnode->prev; + if (bnode->prev) + bnode->prev->next = bnode->next; + if (bnode->parent->child == bnode) + bnode->parent->child = bnode->next; + + if (ops && ops->remove) + ops->remove(gaimbuddylist, bnode); + + gaim_blist_schedule_save(); + + if (bnode->parent->parent != (GaimBlistNode*)g) { + hb = g_new(struct _gaim_hbuddy, 1); + hb->name = g_strdup(gaim_normalize(buddy->account, buddy->name)); + hb->account = buddy->account; + hb->group = bnode->parent->parent; + g_hash_table_remove(gaimbuddylist->buddies, hb); + g_free(hb->name); + g_free(hb); + } + + if (!bnode->parent->child) { + gaim_blist_remove_contact((GaimContact*)bnode->parent); + } else { + gaim_contact_invalidate_priority_buddy((GaimContact*)bnode->parent); + if (ops && ops->update) + ops->update(gaimbuddylist, bnode->parent); + } + } + + if (node && GAIM_BLIST_NODE_IS_BUDDY(node)) { + if (node->next) + node->next->prev = bnode; + bnode->next = node->next; + bnode->prev = node; + bnode->parent = node->parent; + node->next = bnode; + } else { + if (cnode->child) + cnode->child->prev = bnode; + bnode->prev = NULL; + bnode->next = cnode->child; + cnode->child = bnode; + bnode->parent = cnode; + } + + if (GAIM_BUDDY_IS_ONLINE(buddy)) { + ((GaimContact*)bnode->parent)->online++; + if (((GaimContact*)bnode->parent)->online == 1) + ((GaimGroup*)bnode->parent->parent)->online++; + } + if (gaim_account_is_connected(buddy->account)) { + ((GaimContact*)bnode->parent)->currentsize++; + if (((GaimContact*)bnode->parent)->currentsize == 1) + ((GaimGroup*)bnode->parent->parent)->currentsize++; + } + ((GaimContact*)bnode->parent)->totalsize++; + + hb = g_new(struct _gaim_hbuddy, 1); + hb->name = g_strdup(gaim_normalize(buddy->account, buddy->name)); + hb->account = buddy->account; + hb->group = ((GaimBlistNode*)buddy)->parent->parent; + + g_hash_table_replace(gaimbuddylist->buddies, hb, buddy); + + gaim_contact_invalidate_priority_buddy(gaim_buddy_get_contact(buddy)); + + gaim_blist_schedule_save(); + + if (ops && ops->update) + ops->update(gaimbuddylist, (GaimBlistNode*)buddy); + + /* Signal that the buddy has been added */ + gaim_signal_emit(gaim_blist_get_handle(), "buddy-added", buddy); +} + +GaimContact *gaim_contact_new() +{ + GaimBlistUiOps *ops = gaim_blist_get_ui_ops(); + + GaimContact *contact = g_new0(GaimContact, 1); + contact->totalsize = 0; + contact->currentsize = 0; + contact->online = 0; + gaim_blist_node_initialize_settings((GaimBlistNode *)contact); + ((GaimBlistNode *)contact)->type = GAIM_BLIST_CONTACT_NODE; + + if (ops && ops->new_node) + ops->new_node((GaimBlistNode *)contact); + + GAIM_DBUS_REGISTER_POINTER(contact, GaimContact); + return contact; +} + +void gaim_contact_set_alias(GaimContact *contact, const char *alias) +{ + GaimBlistUiOps *ops = gaim_blist_get_ui_ops(); + char *old_alias = contact->alias; + + g_return_if_fail(contact != NULL); + + if ((alias != NULL) && (*alias != '\0')) + contact->alias = g_strdup(alias); + else + contact->alias = NULL; + + gaim_blist_schedule_save(); + + if (ops && ops->update) + ops->update(gaimbuddylist, (GaimBlistNode*)contact); + + gaim_signal_emit(gaim_blist_get_handle(), "blist-node-aliased", + contact, old_alias); + g_free(old_alias); +} + +const char *gaim_contact_get_alias(GaimContact* contact) +{ + g_return_val_if_fail(contact != NULL, NULL); + + if (contact->alias) + return contact->alias; + + return gaim_buddy_get_alias(gaim_contact_get_priority_buddy(contact)); +} + +gboolean gaim_contact_on_account(GaimContact *c, GaimAccount *account) +{ + GaimBlistNode *bnode, *cnode = (GaimBlistNode *) c; + + g_return_val_if_fail(c != NULL, FALSE); + g_return_val_if_fail(account != NULL, FALSE); + + for (bnode = cnode->child; bnode; bnode = bnode->next) { + GaimBuddy *buddy; + + if (! GAIM_BLIST_NODE_IS_BUDDY(bnode)) + continue; + + buddy = (GaimBuddy *)bnode; + if (buddy->account == account) + return TRUE; + } + return FALSE; +} + +void gaim_contact_invalidate_priority_buddy(GaimContact *contact) +{ + g_return_if_fail(contact != NULL); + + contact->priority_valid = FALSE; +} + +GaimGroup *gaim_group_new(const char *name) +{ + GaimBlistUiOps *ops = gaim_blist_get_ui_ops(); + GaimGroup *group; + + g_return_val_if_fail(name != NULL, NULL); + g_return_val_if_fail(*name != '\0', NULL); + + group = gaim_find_group(name); + if (group != NULL) + return group; + + group = g_new0(GaimGroup, 1); + group->name = g_strdup(name); + group->totalsize = 0; + group->currentsize = 0; + group->online = 0; + gaim_blist_node_initialize_settings((GaimBlistNode *)group); + ((GaimBlistNode *)group)->type = GAIM_BLIST_GROUP_NODE; + + if (ops && ops->new_node) + ops->new_node((GaimBlistNode *)group); + + GAIM_DBUS_REGISTER_POINTER(group, GaimGroup); + return group; +} + +void gaim_blist_add_contact(GaimContact *contact, GaimGroup *group, GaimBlistNode *node) +{ + GaimBlistUiOps *ops = gaim_blist_get_ui_ops(); + GaimGroup *g; + GaimBlistNode *gnode, *cnode, *bnode; + + g_return_if_fail(contact != NULL); + g_return_if_fail(GAIM_BLIST_NODE_IS_CONTACT((GaimBlistNode*)contact)); + + if ((GaimBlistNode*)contact == node) + return; + + if (node && (GAIM_BLIST_NODE_IS_CONTACT(node) || + GAIM_BLIST_NODE_IS_CHAT(node))) + g = (GaimGroup*)node->parent; + else if (group) + g = group; + else { + g = gaim_group_new(_("Buddies")); + gaim_blist_add_group(g, + gaim_blist_get_last_sibling(gaimbuddylist->root)); + } + + gnode = (GaimBlistNode*)g; + cnode = (GaimBlistNode*)contact; + + if (cnode->parent) { + if (cnode->parent->child == cnode) + cnode->parent->child = cnode->next; + if (cnode->prev) + cnode->prev->next = cnode->next; + if (cnode->next) + cnode->next->prev = cnode->prev; + + if (cnode->parent != gnode) { + bnode = cnode->child; + while (bnode) { + GaimBlistNode *next_bnode = bnode->next; + GaimBuddy *b = (GaimBuddy*)bnode; + + struct _gaim_hbuddy *hb = g_new(struct _gaim_hbuddy, 1); + hb->name = g_strdup(gaim_normalize(b->account, b->name)); + hb->account = b->account; + hb->group = cnode->parent; + + g_hash_table_remove(gaimbuddylist->buddies, hb); + + if (!gaim_find_buddy_in_group(b->account, b->name, g)) { + hb->group = gnode; + g_hash_table_replace(gaimbuddylist->buddies, hb, b); + + if (b->account->gc) + serv_move_buddy(b, (GaimGroup *)cnode->parent, g); + } else { + gboolean empty_contact = FALSE; + + /* this buddy already exists in the group, so we're + * gonna delete it instead */ + g_free(hb->name); + g_free(hb); + if (b->account->gc) + gaim_account_remove_buddy(b->account, b, (GaimGroup *)cnode->parent); + + if (!cnode->child->next) + empty_contact = TRUE; + gaim_blist_remove_buddy(b); + + /** in gaim_blist_remove_buddy(), if the last buddy in a + * contact is removed, the contact is cleaned up and + * g_free'd, so we mustn't try to reference bnode->next */ + if (empty_contact) + return; + } + bnode = next_bnode; + } + } + + if (contact->online > 0) + ((GaimGroup*)cnode->parent)->online--; + if (contact->currentsize > 0) + ((GaimGroup*)cnode->parent)->currentsize--; + ((GaimGroup*)cnode->parent)->totalsize--; + + if (ops && ops->remove) + ops->remove(gaimbuddylist, cnode); + + gaim_blist_schedule_save(); + } + + if (node && (GAIM_BLIST_NODE_IS_CONTACT(node) || + GAIM_BLIST_NODE_IS_CHAT(node))) { + if (node->next) + node->next->prev = cnode; + cnode->next = node->next; + cnode->prev = node; + cnode->parent = node->parent; + node->next = cnode; + } else { + if (gnode->child) + gnode->child->prev = cnode; + cnode->prev = NULL; + cnode->next = gnode->child; + gnode->child = cnode; + cnode->parent = gnode; + } + + if (contact->online > 0) + g->online++; + if (contact->currentsize > 0) + g->currentsize++; + g->totalsize++; + + gaim_blist_schedule_save(); + + if (ops && ops->update) + { + if (cnode->child) + ops->update(gaimbuddylist, cnode); + + for (bnode = cnode->child; bnode; bnode = bnode->next) + ops->update(gaimbuddylist, bnode); + } +} + +void gaim_blist_merge_contact(GaimContact *source, GaimBlistNode *node) +{ + GaimBlistNode *sourcenode = (GaimBlistNode*)source; + GaimBlistNode *targetnode; + GaimBlistNode *prev, *cur, *next; + GaimContact *target; + + g_return_if_fail(source != NULL); + g_return_if_fail(node != NULL); + + if (GAIM_BLIST_NODE_IS_CONTACT(node)) { + target = (GaimContact *)node; + prev = gaim_blist_get_last_child(node); + } else if (GAIM_BLIST_NODE_IS_BUDDY(node)) { + target = (GaimContact *)node->parent; + prev = node; + } else { + return; + } + + if (source == target || !target) + return; + + targetnode = (GaimBlistNode *)target; + next = sourcenode->child; + + while (next) { + cur = next; + next = cur->next; + if (GAIM_BLIST_NODE_IS_BUDDY(cur)) { + gaim_blist_add_buddy((GaimBuddy *)cur, target, NULL, prev); + prev = cur; + } + } +} + +void gaim_blist_add_group(GaimGroup *group, GaimBlistNode *node) +{ + GaimBlistUiOps *ops; + GaimBlistNode *gnode = (GaimBlistNode*)group; + + g_return_if_fail(group != NULL); + g_return_if_fail(GAIM_BLIST_NODE_IS_GROUP((GaimBlistNode *)group)); + + ops = gaim_blist_get_ui_ops(); + + if (!gaimbuddylist->root) { + gaimbuddylist->root = gnode; + return; + } + + /* if we're moving to overtop of ourselves, do nothing */ + if (gnode == node) + return; + + if (gaim_find_group(group->name)) { + /* This is just being moved */ + + if (ops && ops->remove) + ops->remove(gaimbuddylist, (GaimBlistNode *)group); + + if (gnode == gaimbuddylist->root) + gaimbuddylist->root = gnode->next; + if (gnode->prev) + gnode->prev->next = gnode->next; + if (gnode->next) + gnode->next->prev = gnode->prev; + } + + if (node && GAIM_BLIST_NODE_IS_GROUP(node)) { + gnode->next = node->next; + gnode->prev = node; + if (node->next) + node->next->prev = gnode; + node->next = gnode; + } else { + if (gaimbuddylist->root) + gaimbuddylist->root->prev = gnode; + gnode->next = gaimbuddylist->root; + gnode->prev = NULL; + gaimbuddylist->root = gnode; + } + + gaim_blist_schedule_save(); + + if (ops && ops->update) { + ops->update(gaimbuddylist, gnode); + for (node = gnode->child; node; node = node->next) + ops->update(gaimbuddylist, node); + } +} + +void gaim_blist_remove_contact(GaimContact *contact) +{ + GaimBlistUiOps *ops = gaim_blist_get_ui_ops(); + GaimBlistNode *node, *gnode; + + g_return_if_fail(contact != NULL); + + node = (GaimBlistNode *)contact; + gnode = node->parent; + + if (node->child) { + /* + * If this contact has children then remove them. When the last + * buddy is removed from the contact, the contact is automatically + * deleted. + */ + while (node->child->next) { + gaim_blist_remove_buddy((GaimBuddy*)node->child); + } + /* + * Remove the last buddy and trigger the deletion of the contact. + * It would probably be cleaner if contact-deletion was done after + * a timeout? Or if it had to be done manually, like below? + */ + gaim_blist_remove_buddy((GaimBuddy*)node->child); + } else { + /* Remove the node from its parent */ + if (gnode->child == node) + gnode->child = node->next; + if (node->prev) + node->prev->next = node->next; + if (node->next) + node->next->prev = node->prev; + + gaim_blist_schedule_save(); + + /* Update the UI */ + if (ops && ops->remove) + ops->remove(gaimbuddylist, node); + + /* Delete the node */ + g_hash_table_destroy(contact->node.settings); + GAIM_DBUS_UNREGISTER_POINTER(contact); + g_free(contact); + } +} + +void gaim_blist_remove_buddy(GaimBuddy *buddy) +{ + GaimBlistUiOps *ops = gaim_blist_get_ui_ops(); + GaimBlistNode *node, *cnode, *gnode; + GaimContact *contact; + GaimGroup *group; + struct _gaim_hbuddy hb; + + g_return_if_fail(buddy != NULL); + + node = (GaimBlistNode *)buddy; + cnode = node->parent; + gnode = cnode->parent; + contact = (GaimContact *)cnode; + group = (GaimGroup *)gnode; + + /* Delete any buddy icon. */ + gaim_buddy_icon_uncache(buddy); + + /* Remove the node from its parent */ + if (node->prev) + node->prev->next = node->next; + if (node->next) + node->next->prev = node->prev; + if (cnode->child == node) + cnode->child = node->next; + + /* Adjust size counts */ + if (GAIM_BUDDY_IS_ONLINE(buddy)) { + contact->online--; + if (contact->online == 0) + group->online--; + } + if (gaim_account_is_connected(buddy->account)) { + contact->currentsize--; + if (contact->currentsize == 0) + group->currentsize--; + } + contact->totalsize--; + + gaim_blist_schedule_save(); + + /* Re-sort the contact */ + if (contact->priority == buddy) { + gaim_contact_invalidate_priority_buddy(contact); + if (ops && ops->update) + ops->update(gaimbuddylist, cnode); + } + + /* Remove this buddy from the buddies hash table */ + hb.name = g_strdup(gaim_normalize(buddy->account, buddy->name)); + hb.account = buddy->account; + hb.group = ((GaimBlistNode*)buddy)->parent->parent; + g_hash_table_remove(gaimbuddylist->buddies, &hb); + g_free(hb.name); + + /* Update the UI */ + if (ops && ops->remove) + ops->remove(gaimbuddylist, node); + + /* Signal that the buddy has been removed before freeing the memory for it */ + gaim_signal_emit(gaim_blist_get_handle(), "buddy-removed", buddy); + + /* Delete the node */ + if (buddy->icon != NULL) + gaim_buddy_icon_unref(buddy->icon); + g_hash_table_destroy(buddy->node.settings); + gaim_presence_remove_buddy(buddy->presence, buddy); + gaim_presence_destroy(buddy->presence); + g_free(buddy->name); + g_free(buddy->alias); + g_free(buddy->server_alias); + + GAIM_DBUS_UNREGISTER_POINTER(buddy); + g_free(buddy); + + /* FIXME: Once GaimBuddy is a GObject, timeout callbacks can + * g_object_ref() it when connecting the callback and + * g_object_unref() it in the handler. That way, it won't + * get freed while the timeout is pending and this line can + * be removed. */ + while (g_source_remove_by_user_data((gpointer *)buddy)); + + /* If the contact is empty then remove it */ + if (!cnode->child) + gaim_blist_remove_contact(contact); +} + +void gaim_blist_remove_chat(GaimChat *chat) +{ + GaimBlistUiOps *ops = gaim_blist_get_ui_ops(); + GaimBlistNode *node, *gnode; + GaimGroup *group; + + g_return_if_fail(chat != NULL); + + node = (GaimBlistNode *)chat; + gnode = node->parent; + group = (GaimGroup *)gnode; + + if (gnode != NULL) + { + /* Remove the node from its parent */ + if (gnode->child == node) + gnode->child = node->next; + if (node->prev) + node->prev->next = node->next; + if (node->next) + node->next->prev = node->prev; + + /* Adjust size counts */ + if (gaim_account_is_connected(chat->account)) { + group->online--; + group->currentsize--; + } + group->totalsize--; + + gaim_blist_schedule_save(); + } + + /* Update the UI */ + if (ops && ops->remove) + ops->remove(gaimbuddylist, node); + + /* Delete the node */ + g_hash_table_destroy(chat->components); + g_hash_table_destroy(chat->node.settings); + g_free(chat->alias); + GAIM_DBUS_UNREGISTER_POINTER(chat); + g_free(chat); +} + +void gaim_blist_remove_group(GaimGroup *group) +{ + GaimBlistUiOps *ops = gaim_blist_get_ui_ops(); + GaimBlistNode *node; + GList *l; + + g_return_if_fail(group != NULL); + + node = (GaimBlistNode *)group; + + /* Make sure the group is empty */ + if (node->child) { + char *buf; + int count = 0; + GaimBlistNode *child; + + for (child = node->child; child != NULL; child = child->next) + count++; + + buf = g_strdup_printf(ngettext("%d buddy from group %s was not removed " + "because it belongs to an account which is " + "disabled or offline. This buddy and the " + "group were not removed.\n", + "%d buddies from group %s were not " + "removed because they belong to accounts " + "which are currently disabled or offline. " + "These buddies and the group were not " + "removed.\n", count), + count, group->name); + gaim_notify_error(NULL, NULL, _("Group not removed"), buf); + g_free(buf); + return; + } + + /* Remove the node from its parent */ + if (gaimbuddylist->root == node) + gaimbuddylist->root = node->next; + if (node->prev) + node->prev->next = node->next; + if (node->next) + node->next->prev = node->prev; + + gaim_blist_schedule_save(); + + /* Update the UI */ + if (ops && ops->remove) + ops->remove(gaimbuddylist, node); + + /* Remove the group from all accounts that are online */ + for (l = gaim_connections_get_all(); l != NULL; l = l->next) + { + GaimConnection *gc = (GaimConnection *)l->data; + + if (gaim_connection_get_state(gc) == GAIM_CONNECTED) + gaim_account_remove_group(gaim_connection_get_account(gc), group); + } + + /* Delete the node */ + g_hash_table_destroy(group->node.settings); + g_free(group->name); + GAIM_DBUS_UNREGISTER_POINTER(group); + g_free(group); +} + +GaimBuddy *gaim_contact_get_priority_buddy(GaimContact *contact) +{ + g_return_val_if_fail(contact != NULL, NULL); + + if (!contact->priority_valid) + gaim_contact_compute_priority_buddy(contact); + + return contact->priority; +} + +const char *gaim_buddy_get_alias_only(GaimBuddy *buddy) +{ + g_return_val_if_fail(buddy != NULL, NULL); + + if ((buddy->alias != NULL) && (*buddy->alias != '\0')) { + return buddy->alias; + } else if ((buddy->server_alias != NULL) && + (*buddy->server_alias != '\0')) { + + return buddy->server_alias; + } + + return NULL; +} + + +const char *gaim_buddy_get_contact_alias(GaimBuddy *buddy) +{ + GaimContact *c; + + g_return_val_if_fail(buddy != NULL, NULL); + + /* Search for an alias for the buddy. In order of precedence: */ + /* The buddy alias */ + if (buddy->alias != NULL) + return buddy->alias; + + /* The contact alias */ + c = gaim_buddy_get_contact(buddy); + if ((c != NULL) && (c->alias != NULL)) + return c->alias; + + /* The server alias */ + if ((buddy->server_alias) && (*buddy->server_alias)) + return buddy->server_alias; + + /* The buddy's user name (i.e. no alias) */ + return buddy->name; +} + + +const char *gaim_buddy_get_alias(GaimBuddy *buddy) +{ + g_return_val_if_fail(buddy != NULL, NULL); + + /* Search for an alias for the buddy. In order of precedence: */ + /* The buddy alias */ + if (buddy->alias != NULL) + return buddy->alias; + + /* The server alias */ + if ((buddy->server_alias) && (*buddy->server_alias)) + return buddy->server_alias; + + /* The buddy's user name (i.e. no alias) */ + return buddy->name; +} + +const char *gaim_buddy_get_local_alias(GaimBuddy *buddy) +{ + GaimContact *c; + + g_return_val_if_fail(buddy != NULL, NULL); + + /* Search for an alias for the buddy. In order of precedence: */ + /* The buddy alias */ + if (buddy->alias != NULL) + return buddy->alias; + + /* The contact alias */ + c = gaim_buddy_get_contact(buddy); + if ((c != NULL) && (c->alias != NULL)) + return c->alias; + + /* The buddy's user name (i.e. no alias) */ + return buddy->name; +} + +const char *gaim_chat_get_name(GaimChat *chat) +{ + struct proto_chat_entry *pce; + GList *parts; + char *ret; + + g_return_val_if_fail(chat != NULL, NULL); + + if ((chat->alias != NULL) && (*chat->alias != '\0')) + return chat->alias; + + parts = GAIM_PLUGIN_PROTOCOL_INFO(chat->account->gc->prpl)->chat_info(chat->account->gc); + pce = parts->data; + ret = g_hash_table_lookup(chat->components, pce->identifier); + g_list_foreach(parts, (GFunc)g_free, NULL); + g_list_free(parts); + + return ret; +} + +GaimBuddy *gaim_find_buddy(GaimAccount *account, const char *name) +{ + GaimBuddy *buddy; + struct _gaim_hbuddy hb; + GaimBlistNode *group; + + g_return_val_if_fail(gaimbuddylist != NULL, NULL); + g_return_val_if_fail(account != NULL, NULL); + g_return_val_if_fail((name != NULL) && (*name != '\0'), NULL); + + hb.account = account; + hb.name = g_strdup(gaim_normalize(account, name)); + + for (group = gaimbuddylist->root; group; group = group->next) { + hb.group = group; + if ((buddy = g_hash_table_lookup(gaimbuddylist->buddies, &hb))) { + g_free(hb.name); + return buddy; + } + } + g_free(hb.name); + + return NULL; +} + +GaimBuddy *gaim_find_buddy_in_group(GaimAccount *account, const char *name, + GaimGroup *group) +{ + struct _gaim_hbuddy hb; + GaimBuddy *ret; + + g_return_val_if_fail(gaimbuddylist != NULL, NULL); + g_return_val_if_fail(account != NULL, NULL); + g_return_val_if_fail((name != NULL) && (*name != '\0'), NULL); + + hb.name = g_strdup(gaim_normalize(account, name)); + hb.account = account; + hb.group = (GaimBlistNode*)group; + + ret = g_hash_table_lookup(gaimbuddylist->buddies, &hb); + g_free(hb.name); + + return ret; +} + +GSList *gaim_find_buddies(GaimAccount *account, const char *name) +{ + struct buddy *buddy; + struct _gaim_hbuddy hb; + GaimBlistNode *node; + GSList *ret = NULL; + + g_return_val_if_fail(gaimbuddylist != NULL, NULL); + g_return_val_if_fail(account != NULL, NULL); + g_return_val_if_fail((name != NULL) && (*name != '\0'), NULL); + + hb.name = g_strdup(gaim_normalize(account, name)); + hb.account = account; + + for (node = gaimbuddylist->root; node != NULL; node = node->next) { + hb.group = node; + if ((buddy = g_hash_table_lookup(gaimbuddylist->buddies, &hb)) != NULL) + ret = g_slist_append(ret, buddy); + } + g_free(hb.name); + + return ret; +} + +GaimGroup *gaim_find_group(const char *name) +{ + GaimBlistNode *node; + + g_return_val_if_fail(gaimbuddylist != NULL, NULL); + g_return_val_if_fail((name != NULL) && (*name != '\0'), NULL); + + for (node = gaimbuddylist->root; node != NULL; node = node->next) { + if (!strcmp(((GaimGroup *)node)->name, name)) + return (GaimGroup *)node; + } + + return NULL; +} + +GaimChat * +gaim_blist_find_chat(GaimAccount *account, const char *name) +{ + char *chat_name; + GaimChat *chat; + GaimPlugin *prpl; + GaimPluginProtocolInfo *prpl_info = NULL; + struct proto_chat_entry *pce; + GaimBlistNode *node, *group; + GList *parts; + + g_return_val_if_fail(gaimbuddylist != NULL, NULL); + g_return_val_if_fail((name != NULL) && (*name != '\0'), NULL); + + if (!gaim_account_is_connected(account)) + return NULL; + + prpl = gaim_find_prpl(gaim_account_get_protocol_id(account)); + prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(prpl); + + if (prpl_info->find_blist_chat != NULL) + return prpl_info->find_blist_chat(account, name); + + for (group = gaimbuddylist->root; group != NULL; group = group->next) { + for (node = group->child; node != NULL; node = node->next) { + if (GAIM_BLIST_NODE_IS_CHAT(node)) { + + chat = (GaimChat*)node; + + if (account != chat->account) + continue; + + parts = prpl_info->chat_info( + gaim_account_get_connection(chat->account)); + + pce = parts->data; + chat_name = g_hash_table_lookup(chat->components, + pce->identifier); + + if (chat->account == account && chat_name != NULL && + name != NULL && !strcmp(chat_name, name)) { + + return chat; + } + } + } + } + + return NULL; +} + +GaimGroup * +gaim_chat_get_group(GaimChat *chat) +{ + g_return_val_if_fail(chat != NULL, NULL); + + return (GaimGroup *)(((GaimBlistNode *)chat)->parent); +} + +GaimContact *gaim_buddy_get_contact(GaimBuddy *buddy) +{ + g_return_val_if_fail(buddy != NULL, NULL); + + return (GaimContact*)((GaimBlistNode*)buddy)->parent; +} + +GaimPresence *gaim_buddy_get_presence(const GaimBuddy *buddy) +{ + g_return_val_if_fail(buddy != NULL, NULL); + return buddy->presence; +} + +GaimGroup *gaim_buddy_get_group(GaimBuddy *buddy) +{ + g_return_val_if_fail(buddy != NULL, NULL); + + if (((GaimBlistNode *)buddy)->parent == NULL) + return NULL; + + return (GaimGroup *)(((GaimBlistNode*)buddy)->parent->parent); +} + +GSList *gaim_group_get_accounts(GaimGroup *group) +{ + GSList *l = NULL; + GaimBlistNode *gnode, *cnode, *bnode; + + gnode = (GaimBlistNode *)group; + + for (cnode = gnode->child; cnode; cnode = cnode->next) { + if (GAIM_BLIST_NODE_IS_CHAT(cnode)) { + if (!g_slist_find(l, ((GaimChat *)cnode)->account)) + l = g_slist_append(l, ((GaimChat *)cnode)->account); + } else if (GAIM_BLIST_NODE_IS_CONTACT(cnode)) { + for (bnode = cnode->child; bnode; bnode = bnode->next) { + if (GAIM_BLIST_NODE_IS_BUDDY(bnode)) { + if (!g_slist_find(l, ((GaimBuddy *)bnode)->account)) + l = g_slist_append(l, ((GaimBuddy *)bnode)->account); + } + } + } + } + + return l; +} + +void gaim_blist_add_account(GaimAccount *account) +{ + GaimBlistUiOps *ops = gaim_blist_get_ui_ops(); + GaimBlistNode *gnode, *cnode, *bnode; + + g_return_if_fail(gaimbuddylist != NULL); + + if (!ops || !ops->update) + return; + + for (gnode = gaimbuddylist->root; gnode; gnode = gnode->next) { + if (!GAIM_BLIST_NODE_IS_GROUP(gnode)) + continue; + for (cnode = gnode->child; cnode; cnode = cnode->next) { + if (GAIM_BLIST_NODE_IS_CONTACT(cnode)) { + gboolean recompute = FALSE; + for (bnode = cnode->child; bnode; bnode = bnode->next) { + if (GAIM_BLIST_NODE_IS_BUDDY(bnode) && + ((GaimBuddy*)bnode)->account == account) { + recompute = TRUE; + ((GaimContact*)cnode)->currentsize++; + if (((GaimContact*)cnode)->currentsize == 1) + ((GaimGroup*)gnode)->currentsize++; + ops->update(gaimbuddylist, bnode); + } + } + if (recompute || + gaim_blist_node_get_bool(cnode, "show_offline")) { + gaim_contact_invalidate_priority_buddy((GaimContact*)cnode); + ops->update(gaimbuddylist, cnode); + } + } else if (GAIM_BLIST_NODE_IS_CHAT(cnode) && + ((GaimChat*)cnode)->account == account) { + ((GaimGroup *)gnode)->online++; + ((GaimGroup *)gnode)->currentsize++; + ops->update(gaimbuddylist, cnode); + } + } + ops->update(gaimbuddylist, gnode); + } +} + +void gaim_blist_remove_account(GaimAccount *account) +{ + GaimBlistUiOps *ops = gaim_blist_get_ui_ops(); + GaimBlistNode *gnode, *cnode, *bnode; + GaimBuddy *buddy; + GaimChat *chat; + GaimContact *contact; + GaimGroup *group; + GList *list = NULL, *iter = NULL; + + g_return_if_fail(gaimbuddylist != NULL); + + for (gnode = gaimbuddylist->root; gnode; gnode = gnode->next) { + if (!GAIM_BLIST_NODE_IS_GROUP(gnode)) + continue; + + group = (GaimGroup *)gnode; + + for (cnode = gnode->child; cnode; cnode = cnode->next) { + if (GAIM_BLIST_NODE_IS_CONTACT(cnode)) { + gboolean recompute = FALSE; + contact = (GaimContact *)cnode; + + for (bnode = cnode->child; bnode; bnode = bnode->next) { + if (!GAIM_BLIST_NODE_IS_BUDDY(bnode)) + continue; + + buddy = (GaimBuddy *)bnode; + if (account == buddy->account) { + GaimPresence *presence; + recompute = TRUE; + + presence = gaim_buddy_get_presence(buddy); + + if(gaim_presence_is_online(presence)) { + contact->online--; + if (contact->online == 0) + group->online--; + + gaim_blist_node_set_int(&buddy->node, + "last_seen", time(NULL)); + } + + contact->currentsize--; + if (contact->currentsize == 0) + group->currentsize--; + + if (!g_list_find(list, presence)) + list = g_list_prepend(list, presence); + + if (ops && ops->remove) + ops->remove(gaimbuddylist, bnode); + } + } + if (recompute) { + gaim_contact_invalidate_priority_buddy(contact); + if (ops && ops->update) + ops->update(gaimbuddylist, cnode); + } + } else if (GAIM_BLIST_NODE_IS_CHAT(cnode)) { + chat = (GaimChat *)cnode; + + if(chat->account == account) { + group->currentsize--; + group->online--; + + if (ops && ops->remove) + ops->remove(gaimbuddylist, cnode); + } + } + } + } + + for (iter = list; iter; iter = iter->next) + { + gaim_presence_set_status_active(iter->data, "offline", TRUE); + } + g_list_free(list); +} + +gboolean gaim_group_on_account(GaimGroup *g, GaimAccount *account) +{ + GaimBlistNode *cnode; + for (cnode = ((GaimBlistNode *)g)->child; cnode; cnode = cnode->next) { + if (GAIM_BLIST_NODE_IS_CONTACT(cnode)) { + if(gaim_contact_on_account((GaimContact *) cnode, account)) + return TRUE; + } else if (GAIM_BLIST_NODE_IS_CHAT(cnode)) { + GaimChat *chat = (GaimChat *)cnode; + if ((!account && gaim_account_is_connected(chat->account)) + || chat->account == account) + return TRUE; + } + } + return FALSE; +} + +void +gaim_blist_request_add_buddy(GaimAccount *account, const char *username, + const char *group, const char *alias) +{ + GaimBlistUiOps *ui_ops; + + ui_ops = gaim_blist_get_ui_ops(); + + if (ui_ops != NULL && ui_ops->request_add_buddy != NULL) + ui_ops->request_add_buddy(account, username, group, alias); +} + +void +gaim_blist_request_add_chat(GaimAccount *account, GaimGroup *group, + const char *alias, const char *name) +{ + GaimBlistUiOps *ui_ops; + + ui_ops = gaim_blist_get_ui_ops(); + + if (ui_ops != NULL && ui_ops->request_add_chat != NULL) + ui_ops->request_add_chat(account, group, alias, name); +} + +void +gaim_blist_request_add_group(void) +{ + GaimBlistUiOps *ui_ops; + + ui_ops = gaim_blist_get_ui_ops(); + + if (ui_ops != NULL && ui_ops->request_add_group != NULL) + ui_ops->request_add_group(); +} + +static void +gaim_blist_node_setting_free(gpointer data) +{ + GaimValue *value; + + value = (GaimValue *)data; + + gaim_value_destroy(value); +} + +static void gaim_blist_node_initialize_settings(GaimBlistNode *node) +{ + if (node->settings) + return; + + node->settings = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, + (GDestroyNotify)gaim_blist_node_setting_free); +} + +void gaim_blist_node_remove_setting(GaimBlistNode *node, const char *key) +{ + g_return_if_fail(node != NULL); + g_return_if_fail(node->settings != NULL); + g_return_if_fail(key != NULL); + + g_hash_table_remove(node->settings, key); + + gaim_blist_schedule_save(); +} + +void +gaim_blist_node_set_flags(GaimBlistNode *node, GaimBlistNodeFlags flags) +{ + g_return_if_fail(node != NULL); + + node->flags = flags; +} + +GaimBlistNodeFlags +gaim_blist_node_get_flags(GaimBlistNode *node) +{ + g_return_val_if_fail(node != NULL, 0); + + return node->flags; +} + +void +gaim_blist_node_set_bool(GaimBlistNode* node, const char *key, gboolean data) +{ + GaimValue *value; + + g_return_if_fail(node != NULL); + g_return_if_fail(node->settings != NULL); + g_return_if_fail(key != NULL); + + value = gaim_value_new(GAIM_TYPE_BOOLEAN); + gaim_value_set_boolean(value, data); + + g_hash_table_replace(node->settings, g_strdup(key), value); + + gaim_blist_schedule_save(); +} + +gboolean +gaim_blist_node_get_bool(GaimBlistNode* node, const char *key) +{ + GaimValue *value; + + g_return_val_if_fail(node != NULL, FALSE); + g_return_val_if_fail(node->settings != NULL, FALSE); + g_return_val_if_fail(key != NULL, FALSE); + + value = g_hash_table_lookup(node->settings, key); + + if (value == NULL) + return FALSE; + + g_return_val_if_fail(gaim_value_get_type(value) == GAIM_TYPE_BOOLEAN, FALSE); + + return gaim_value_get_boolean(value); +} + +void +gaim_blist_node_set_int(GaimBlistNode* node, const char *key, int data) +{ + GaimValue *value; + + g_return_if_fail(node != NULL); + g_return_if_fail(node->settings != NULL); + g_return_if_fail(key != NULL); + + value = gaim_value_new(GAIM_TYPE_INT); + gaim_value_set_int(value, data); + + g_hash_table_replace(node->settings, g_strdup(key), value); + + gaim_blist_schedule_save(); +} + +int +gaim_blist_node_get_int(GaimBlistNode* node, const char *key) +{ + GaimValue *value; + + g_return_val_if_fail(node != NULL, 0); + g_return_val_if_fail(node->settings != NULL, 0); + g_return_val_if_fail(key != NULL, 0); + + value = g_hash_table_lookup(node->settings, key); + + if (value == NULL) + return 0; + + g_return_val_if_fail(gaim_value_get_type(value) == GAIM_TYPE_INT, 0); + + return gaim_value_get_int(value); +} + +void +gaim_blist_node_set_string(GaimBlistNode* node, const char *key, const char *data) +{ + GaimValue *value; + + g_return_if_fail(node != NULL); + g_return_if_fail(node->settings != NULL); + g_return_if_fail(key != NULL); + + value = gaim_value_new(GAIM_TYPE_STRING); + gaim_value_set_string(value, data); + + g_hash_table_replace(node->settings, g_strdup(key), value); + + gaim_blist_schedule_save(); +} + +const char * +gaim_blist_node_get_string(GaimBlistNode* node, const char *key) +{ + GaimValue *value; + + g_return_val_if_fail(node != NULL, NULL); + g_return_val_if_fail(node->settings != NULL, NULL); + g_return_val_if_fail(key != NULL, NULL); + + value = g_hash_table_lookup(node->settings, key); + + if (value == NULL) + return NULL; + + g_return_val_if_fail(gaim_value_get_type(value) == GAIM_TYPE_STRING, NULL); + + return gaim_value_get_string(value); +} + +GList * +gaim_blist_node_get_extended_menu(GaimBlistNode *n) +{ + GList *menu = NULL; + + g_return_val_if_fail(n != NULL, NULL); + + gaim_signal_emit(gaim_blist_get_handle(), + "blist-node-extended-menu", + n, &menu); + return menu; +} + +int gaim_blist_get_group_size(GaimGroup *group, gboolean offline) +{ + if (!group) + return 0; + + return offline ? group->totalsize : group->currentsize; +} + +int gaim_blist_get_group_online_count(GaimGroup *group) +{ + if (!group) + return 0; + + return group->online; +} + +void +gaim_blist_set_ui_ops(GaimBlistUiOps *ops) +{ + blist_ui_ops = ops; +} + +GaimBlistUiOps * +gaim_blist_get_ui_ops(void) +{ + return blist_ui_ops; +} + + +void * +gaim_blist_get_handle(void) +{ + static int handle; + + return &handle; +} + +void +gaim_blist_init(void) +{ + void *handle = gaim_blist_get_handle(); + + gaim_signal_register(handle, "buddy-status-changed", + gaim_marshal_VOID__POINTER_POINTER_POINTER, NULL, + 3, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_BLIST_BUDDY), + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_STATUS), + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_STATUS)); + gaim_signal_register(handle, "buddy-privacy-changed", + gaim_marshal_VOID__POINTER, NULL, + 1, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_BLIST_BUDDY)); + + gaim_signal_register(handle, "buddy-idle-changed", + gaim_marshal_VOID__POINTER_INT_INT, NULL, + 3, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_BLIST_BUDDY), + gaim_value_new(GAIM_TYPE_INT), + gaim_value_new(GAIM_TYPE_INT)); + + + gaim_signal_register(handle, "buddy-signed-on", + gaim_marshal_VOID__POINTER, NULL, 1, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_BLIST_BUDDY)); + + gaim_signal_register(handle, "buddy-signed-off", + gaim_marshal_VOID__POINTER, NULL, 1, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_BLIST_BUDDY)); + + gaim_signal_register(handle, "buddy-added", + gaim_marshal_VOID__POINTER, NULL, 1, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_BLIST_BUDDY)); + + gaim_signal_register(handle, "buddy-removed", + gaim_marshal_VOID__POINTER, NULL, 1, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_BLIST_BUDDY)); + + gaim_signal_register(handle, "buddy-icon-changed", + gaim_marshal_VOID__POINTER, NULL, 1, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_BLIST_BUDDY)); + + gaim_signal_register(handle, "update-idle", gaim_marshal_VOID, NULL, 0); + + gaim_signal_register(handle, "blist-node-extended-menu", + gaim_marshal_VOID__POINTER_POINTER, NULL, 2, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_BLIST_NODE), + gaim_value_new(GAIM_TYPE_BOXED, "GList **")); + + gaim_signal_register(handle, "blist-node-aliased", + gaim_marshal_VOID__POINTER_POINTER, NULL, 2, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_BLIST_NODE), + gaim_value_new(GAIM_TYPE_STRING)); +} + +void +gaim_blist_uninit(void) +{ + if (save_timer != 0) + { + gaim_timeout_remove(save_timer); + save_timer = 0; + gaim_blist_sync(); + } + + gaim_signals_unregister_by_instance(gaim_blist_get_handle()); +} diff -r d10dda2777a9 -r b63ebf84c42b core/blist.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/blist.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,903 @@ +/** + * @file blist.h Buddy List API + * @ingroup core + * + * 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 + */ +#ifndef _GAIM_BLIST_H_ +#define _GAIM_BLIST_H_ + +/* I can't believe I let ChipX86 inspire me to write good code. -Sean */ + +#include + +typedef struct _GaimBuddyList GaimBuddyList; +typedef struct _GaimBlistUiOps GaimBlistUiOps; +typedef struct _GaimBlistNode GaimBlistNode; + +typedef struct _GaimChat GaimChat; +typedef struct _GaimGroup GaimGroup; +typedef struct _GaimContact GaimContact; +typedef struct _GaimBuddy GaimBuddy; + +/**************************************************************************/ +/* Enumerations */ +/**************************************************************************/ +typedef enum +{ + GAIM_BLIST_GROUP_NODE, + GAIM_BLIST_CONTACT_NODE, + GAIM_BLIST_BUDDY_NODE, + GAIM_BLIST_CHAT_NODE, + GAIM_BLIST_OTHER_NODE + +} GaimBlistNodeType; + +#define GAIM_BLIST_NODE_IS_CHAT(n) ((n)->type == GAIM_BLIST_CHAT_NODE) +#define GAIM_BLIST_NODE_IS_BUDDY(n) ((n)->type == GAIM_BLIST_BUDDY_NODE) +#define GAIM_BLIST_NODE_IS_CONTACT(n) ((n)->type == GAIM_BLIST_CONTACT_NODE) +#define GAIM_BLIST_NODE_IS_GROUP(n) ((n)->type == GAIM_BLIST_GROUP_NODE) + +#define GAIM_BUDDY_IS_ONLINE(b) \ + ((b) != NULL && gaim_account_is_connected((b)->account) && \ + gaim_presence_is_online(gaim_buddy_get_presence(b))) + +typedef enum +{ + GAIM_BLIST_NODE_FLAG_NO_SAVE = 1 /**< node should not be saved with the buddy list */ + +} GaimBlistNodeFlags; + +#define GAIM_BLIST_NODE_HAS_FLAG(b, f) ((b)->flags & (f)) +#define GAIM_BLIST_NODE_SHOULD_SAVE(b) (! GAIM_BLIST_NODE_HAS_FLAG(b, GAIM_BLIST_NODE_FLAG_NO_SAVE)) + +#define GAIM_BLIST_NODE_NAME(n) ((n)->type == GAIM_BLIST_CHAT_NODE ? gaim_chat_get_name((GaimChat*)n) : \ + (n)->type == GAIM_BLIST_BUDDY_NODE ? gaim_buddy_get_name((GaimBuddy*)n) : NULL) + +#include "account.h" +#include "buddyicon.h" +#include "status.h" + +/**************************************************************************/ +/* Data Structures */ +/**************************************************************************/ + +/** + * A Buddy list node. This can represent a group, a buddy, or anything else. + * This is a base class for struct buddy and struct group and for anything + * else that wants to put itself in the buddy list. */ +struct _GaimBlistNode { + GaimBlistNodeType type; /**< The type of node this is */ + GaimBlistNode *prev; /**< The sibling before this buddy. */ + GaimBlistNode *next; /**< The sibling after this buddy. */ + GaimBlistNode *parent; /**< The parent of this node */ + GaimBlistNode *child; /**< The child of this node */ + GHashTable *settings; /**< per-node settings */ + void *ui_data; /**< The UI can put data here. */ + GaimBlistNodeFlags flags; /**< The buddy flags */ +}; + +/** + * A buddy. This contains everything Gaim will ever need to know about someone on the buddy list. Everything. + */ +struct _GaimBuddy { + GaimBlistNode node; /**< The node that this buddy inherits from */ + char *name; /**< The screenname of the buddy. */ + char *alias; /**< The user-set alias of the buddy */ + char *server_alias; /**< The server-specified alias of the buddy. (i.e. MSN "Friendly Names") */ + void *proto_data; /**< This allows the prpl to associate whatever data it wants with a buddy */ + GaimBuddyIcon *icon; /**< The buddy icon. */ + GaimAccount *account; /**< the account this buddy belongs to */ + GaimPresence *presence; +}; + +/** + * A contact. This contains everything Gaim will ever need to know about a contact. + */ +struct _GaimContact { + GaimBlistNode node; /**< The node that this contact inherits from. */ + char *alias; /**< The user-set alias of the contact */ + int totalsize; /**< The number of buddies in this contact */ + int currentsize; /**< The number of buddies in this contact corresponding to online accounts */ + int online; /**< The number of buddies in this contact who are currently online */ + GaimBuddy *priority; /**< The "top" buddy for this contact */ + gboolean priority_valid; /**< Is priority valid? */ +}; + + +/** + * A group. This contains everything Gaim will ever need to know about a group. + */ +struct _GaimGroup { + GaimBlistNode node; /**< The node that this group inherits from */ + char *name; /**< The name of this group. */ + int totalsize; /**< The number of chats and contacts in this group */ + int currentsize; /**< The number of chats and contacts in this group corresponding to online accounts */ + int online; /**< The number of chats and contacts in this group who are currently online */ +}; + +/** + * A chat. This contains everything Gaim needs to put a chat room in the + * buddy list. + */ +struct _GaimChat { + GaimBlistNode node; /**< The node that this chat inherits from */ + char *alias; /**< The display name of this chat. */ + GHashTable *components; /**< the stuff the protocol needs to know to join the chat */ + GaimAccount *account; /**< The account this chat is attached to */ +}; + + +/** + * The Buddy List + */ +struct _GaimBuddyList { + GaimBlistNode *root; /**< The first node in the buddy list */ + GHashTable *buddies; /**< Every buddy in this list */ + void *ui_data; /**< UI-specific data. */ +}; + +/** + * Buddy list UI operations. + * + * Any UI representing a buddy list must assign a filled-out GaimBlistUiOps + * structure to the buddy list core. + */ +struct _GaimBlistUiOps +{ + void (*new_list)(GaimBuddyList *list); /**< Sets UI-specific data on a buddy list. */ + void (*new_node)(GaimBlistNode *node); /**< Sets UI-specific data on a node. */ + void (*show)(GaimBuddyList *list); /**< The core will call this when it's finished doing its core stuff */ + void (*update)(GaimBuddyList *list, + GaimBlistNode *node); /**< This will update a node in the buddy list. */ + void (*remove)(GaimBuddyList *list, + GaimBlistNode *node); /**< This removes a node from the list */ + void (*destroy)(GaimBuddyList *list); /**< When the list gets destroyed, this gets called to destroy the UI. */ + void (*set_visible)(GaimBuddyList *list, + gboolean show); /**< Hides or unhides the buddy list */ + void (*request_add_buddy)(GaimAccount *account, const char *username, + const char *group, const char *alias); + void (*request_add_chat)(GaimAccount *account, GaimGroup *group, + const char *alias, const char *name); + void (*request_add_group)(void); +}; + +#ifdef __cplusplus +extern "C" { +#endif + +/**************************************************************************/ +/** @name Buddy List API */ +/**************************************************************************/ +/*@{*/ + +/** + * Creates a new buddy list + * + * @return The new buddy list. + */ +GaimBuddyList *gaim_blist_new(void); + +/** + * Sets the main buddy list. + * + * @param blist The buddy list you want to use. + */ +void gaim_set_blist(GaimBuddyList *blist); + +/** + * Returns the main buddy list. + * + * @return The main buddy list. + */ +GaimBuddyList *gaim_get_blist(void); + +/** + * Returns the root node of the main buddy list. + * + * @return The root node. + */ +GaimBlistNode *gaim_blist_get_root(void); + +/** + * Returns the next node of a given node. This function is to be used to iterate + * over the tree returned by gaim_get_blist. + * + * @param node A node. + * @param offline Whether to include nodes for offline accounts + * @return The next node + */ +GaimBlistNode *gaim_blist_node_next(GaimBlistNode *node, gboolean offline); + +/** + * Shows the buddy list, creating a new one if necessary. + */ +void gaim_blist_show(void); + + +/** + * Destroys the buddy list window. + */ +void gaim_blist_destroy(void); + +/** + * Hides or unhides the buddy list. + * + * @param show Whether or not to show the buddy list + */ +void gaim_blist_set_visible(gboolean show); + +/** + * Updates a buddy's status. + * + * @param buddy The buddy whose status has changed. + * @param old_status The status from which we are changing. + */ +void gaim_blist_update_buddy_status(GaimBuddy *buddy, GaimStatus *old_status); + +/** + * Updates a buddy's icon. + * + * @param buddy The buddy whose buddy icon has changed + */ +void gaim_blist_update_buddy_icon(GaimBuddy *buddy); + +/** + * Renames a buddy in the buddy list. + * + * @param buddy The buddy whose name will be changed. + * @param name The new name of the buddy. + */ +void gaim_blist_rename_buddy(GaimBuddy *buddy, const char *name); + +/** + * Aliases a contact in the buddy list. + * + * @param contact The contact whose alias will be changed. + * @param alias The contact's alias. + */ +void gaim_blist_alias_contact(GaimContact *contact, const char *alias); + +/** + * Aliases a buddy in the buddy list. + * + * @param buddy The buddy whose alias will be changed. + * @param alias The buddy's alias. + */ +void gaim_blist_alias_buddy(GaimBuddy *buddy, const char *alias); + +/** + * Sets the server-sent alias of a buddy in the buddy list. + * PRPLs should call serv_got_alias() instead of this. + * + * @param buddy The buddy whose alias will be changed. + * @param alias The buddy's "official" alias. + */ +void gaim_blist_server_alias_buddy(GaimBuddy *buddy, const char *alias); + +/** + * Aliases a chat in the buddy list. + * + * @param chat The chat whose alias will be changed. + * @param alias The chat's new alias. + */ +void gaim_blist_alias_chat(GaimChat *chat, const char *alias); + +/** + * Renames a group + * + * @param group The group to rename + * @param name The new name + */ +void gaim_blist_rename_group(GaimGroup *group, const char *name); + +/** + * Creates a new chat for the buddy list + * + * @param account The account this chat will get added to + * @param alias The alias of the new chat + * @param components The info the prpl needs to join the chat + * @return A newly allocated chat + */ +GaimChat *gaim_chat_new(GaimAccount *account, const char *alias, GHashTable *components); + +/** + * Adds a new chat to the buddy list. + * + * The chat will be inserted right after node or appended to the end + * of group if node is NULL. If both are NULL, the buddy will be added to + * the "Chats" group. + * + * @param chat The new chat who gets added + * @param group The group to add the new chat to. + * @param node The insertion point + */ +void gaim_blist_add_chat(GaimChat *chat, GaimGroup *group, GaimBlistNode *node); + +/** + * Creates a new buddy + * + * @param account The account this buddy will get added to + * @param screenname The screenname of the new buddy + * @param alias The alias of the new buddy (or NULL if unaliased) + * @return A newly allocated buddy + */ +GaimBuddy *gaim_buddy_new(GaimAccount *account, const char *screenname, const char *alias); + +/** + * Sets a buddy's icon. + * + * This should only be called from within Gaim. You probably want to + * call gaim_buddy_icon_set_data(). + * + * @param buddy The buddy. + * @param icon The buddy icon. + * + * @see gaim_buddy_icon_set_data() + */ +void gaim_buddy_set_icon(GaimBuddy *buddy, GaimBuddyIcon *icon); + +/** + * Returns a buddy's account. + * + * @param buddy The buddy. + * + * @return The account + */ +GaimAccount *gaim_buddy_get_account(const GaimBuddy *buddy); + +/** + * Returns a buddy's name + * + * @param buddy The buddy. + * + * @return The name. + */ +const char *gaim_buddy_get_name(const GaimBuddy *buddy); + +/** + * Returns a buddy's icon. + * + * @param buddy The buddy. + * + * @return The buddy icon. + */ +GaimBuddyIcon *gaim_buddy_get_icon(const GaimBuddy *buddy); + +/** + * Returns a buddy's contact. + * + * @param buddy The buddy. + * + * @return The buddy's contact. + */ +GaimContact *gaim_buddy_get_contact(GaimBuddy *buddy); + +/** + * Returns a buddy's presence. + * + * @param buddy The buddy. + * + * @return The buddy's presence. + */ +GaimPresence *gaim_buddy_get_presence(const GaimBuddy *buddy); + +/** + * Adds a new buddy to the buddy list. + * + * The buddy will be inserted right after node or prepended to the + * group if node is NULL. If both are NULL, the buddy will be added to + * the "Buddies" group. + * + * @param buddy The new buddy who gets added + * @param contact The optional contact to place the buddy in. + * @param group The group to add the new buddy to. + * @param node The insertion point + */ +void gaim_blist_add_buddy(GaimBuddy *buddy, GaimContact *contact, GaimGroup *group, GaimBlistNode *node); + +/** + * Creates a new group + * + * You can't have more than one group with the same name. Sorry. If you pass + * this the * name of a group that already exists, it will return that group. + * + * @param name The name of the new group + * @return A new group struct +*/ +GaimGroup *gaim_group_new(const char *name); + +/** + * Adds a new group to the buddy list. + * + * The new group will be inserted after insert or prepended to the list if + * node is NULL. + * + * @param group The group + * @param node The insertion point + */ +void gaim_blist_add_group(GaimGroup *group, GaimBlistNode *node); + +/** + * Creates a new contact + * + * @return A new contact struct + */ +GaimContact *gaim_contact_new(void); + +/** + * Adds a new contact to the buddy list. + * + * The new contact will be inserted after insert or prepended to the list if + * node is NULL. + * + * @param contact The contact + * @param group The group to add the contact to + * @param node The insertion point + */ +void gaim_blist_add_contact(GaimContact *contact, GaimGroup *group, GaimBlistNode *node); + +/** + * Merges two contacts + * + * All of the buddies from source will be moved to target + * + * @param source The contact to merge + * @param node The place to merge to (a buddy or contact) + */ +void gaim_blist_merge_contact(GaimContact *source, GaimBlistNode *node); + +/** + * Returns the highest priority buddy for a given contact. + * + * @param contact The contact + * @return The highest priority buddy + */ +GaimBuddy *gaim_contact_get_priority_buddy(GaimContact *contact); + +/** + * Sets the alias for a contact. + * + * @param contact The contact + * @param alias The alias to set, or NULL to unset + */ +void gaim_contact_set_alias(GaimContact *contact, const char *alias); + +/** + * Gets the alias for a contact. + * + * @param contact The contact + * @return The alias, or NULL if it is not set. + */ +const char *gaim_contact_get_alias(GaimContact *contact); + +/** + * Determines whether an account owns any buddies in a given contact + * + * @param contact The contact to search through. + * @param account The account. + * + * @return TRUE if there are any buddies from account in the contact, or FALSE otherwise. + */ +gboolean gaim_contact_on_account(GaimContact *contact, GaimAccount *account); + +/** + * Invalidates the priority buddy so that the next call to + * gaim_contact_get_priority_buddy recomputes it. + * + * @param contact The contact + */ +void gaim_contact_invalidate_priority_buddy(GaimContact *contact); +/** + * Removes a buddy from the buddy list and frees the memory allocated to it. + * + * @param buddy The buddy to be removed + */ +void gaim_blist_remove_buddy(GaimBuddy *buddy); + +/** + * Removes a contact, and any buddies it contains, and frees the memory + * allocated to it. + * + * @param contact The contact to be removed + */ +void gaim_blist_remove_contact(GaimContact *contact); + +/** + * Removes a chat from the buddy list and frees the memory allocated to it. + * + * @param chat The chat to be removed + */ +void gaim_blist_remove_chat(GaimChat *chat); + +/** + * Removes a group from the buddy list and frees the memory allocated to it and to + * its children + * + * @param group The group to be removed + */ +void gaim_blist_remove_group(GaimGroup *group); + +/** + * Returns the alias of a buddy. + * + * @param buddy The buddy whose name will be returned. + * @return The alias (if set), server alias (if set), + * or NULL. + */ +const char *gaim_buddy_get_alias_only(GaimBuddy *buddy); + + +/** + * Returns the correct name to display for a buddy, taking the contact alias + * into account. In order of precedence: the buddy's alias; the buddy's + * contact alias; the buddy's server alias; the buddy's user name. + * + * @param buddy The buddy whose name will be returned + * @return The appropriate name or alias, or NULL. + * + */ +const char *gaim_buddy_get_contact_alias(GaimBuddy *buddy); + +/** + * Returns the correct alias for this user, ignoring server aliases. Used + * when a user-recognizable name is required. In order: buddy's alias; buddy's + * contact alias; buddy's user name. + * + * @param buddy The buddy whose alias will be returned. + * @return The appropriate name or alias. + */ +const char *gaim_buddy_get_local_alias(GaimBuddy *buddy); + +/** + * Returns the correct name to display for a buddy. In order of precedence: + * the buddy's alias; the buddy's server alias; the buddy's contact alias; + * the buddy's user name. + * + * @param buddy The buddy whose name will be returned. + * @return The appropriate name or alias, or NULL + */ +const char *gaim_buddy_get_alias(GaimBuddy *buddy); + +/** + * Returns the correct name to display for a blist chat. + * + * @param chat The chat whose name will be returned. + * @return The alias (if set), or first component value. + */ +const char *gaim_chat_get_name(GaimChat *chat); + +/** + * Finds the buddy struct given a screenname and an account + * + * @param account The account this buddy belongs to + * @param name The buddy's screenname + * @return The buddy or NULL if the buddy does not exist + */ +GaimBuddy *gaim_find_buddy(GaimAccount *account, const char *name); + +/** + * Finds the buddy struct given a screenname, an account, and a group + * + * @param account The account this buddy belongs to + * @param name The buddy's screenname + * @param group The group to look in + * @return The buddy or NULL if the buddy does not exist in the group + */ +GaimBuddy *gaim_find_buddy_in_group(GaimAccount *account, const char *name, + GaimGroup *group); + +/** + * Finds all GaimBuddy structs given a screenname and an account + * + * @param account The account this buddy belongs to + * @param name The buddy's screenname + * + * @return A GSList of buddies (which must be freed), or NULL if the buddy doesn't exist + */ +GSList *gaim_find_buddies(GaimAccount *account, const char *name); + + +/** + * Finds a group by name + * + * @param name The groups name + * @return The group or NULL if the group does not exist + */ +GaimGroup *gaim_find_group(const char *name); + +/** + * Finds a chat by name. + * + * @param account The chat's account. + * @param name The chat's name. + * + * @return The chat, or @c NULL if the chat does not exist. + */ +GaimChat *gaim_blist_find_chat(GaimAccount *account, const char *name); + +/** + * Returns the group of which the chat is a member. + * + * @param chat The chat. + * + * @return The parent group, or @c NULL if the chat is not in a group. + */ +GaimGroup *gaim_chat_get_group(GaimChat *chat); + +/** + * Returns the group of which the buddy is a member. + * + * @param buddy The buddy + * @return The group or NULL if the buddy is not in a group + */ +GaimGroup *gaim_buddy_get_group(GaimBuddy *buddy); + + +/** + * Returns a list of accounts that have buddies in this group + * + * @param g The group + * + * @return A list of gaim_accounts + */ +GSList *gaim_group_get_accounts(GaimGroup *g); + +/** + * Determines whether an account owns any buddies in a given group + * + * @param g The group to search through. + * @param account The account. + * + * @return TRUE if there are any buddies in the group, or FALSE otherwise. + */ +gboolean gaim_group_on_account(GaimGroup *g, GaimAccount *account); + +/** + * Called when an account gets signed on. Tells the UI to update all the + * buddies. + * + * @param account The account + */ +void gaim_blist_add_account(GaimAccount *account); + + +/** + * Called when an account gets signed off. Sets the presence of all the buddies to 0 + * and tells the UI to update them. + * + * @param account The account + */ +void gaim_blist_remove_account(GaimAccount *account); + + +/** + * Determines the total size of a group + * + * @param group The group + * @param offline Count buddies in offline accounts + * @return The number of buddies in the group + */ +int gaim_blist_get_group_size(GaimGroup *group, gboolean offline); + +/** + * Determines the number of online buddies in a group + * + * @param group The group + * @return The number of online buddies in the group, or 0 if the group is NULL + */ +int gaim_blist_get_group_online_count(GaimGroup *group); + +/*@}*/ + +/****************************************************************************************/ +/** @name Buddy list file management API */ +/****************************************************************************************/ + +/** + * Loads the buddy list from ~/.gaim/blist.xml. + */ +void gaim_blist_load(void); + +/** + * Schedule a save of the blist.xml file. This is used by the privacy + * API whenever the privacy settings are changed. If you make a change + * to blist.xml using one of the functions in the buddy list API, then + * the buddy list is saved automatically, so you should not need to + * call this. + */ +void gaim_blist_schedule_save(void); + +/** + * Requests from the user information needed to add a buddy to the + * buddy list. + * + * @param account The account the buddy is added to. + * @param username The username of the buddy. + * @param group The name of the group to place the buddy in. + * @param alias The optional alias for the buddy. + */ +void gaim_blist_request_add_buddy(GaimAccount *account, const char *username, + const char *group, const char *alias); + +/** + * Requests from the user information needed to add a chat to the + * buddy list. + * + * @param account The account the buddy is added to. + * @param group The optional group to add the chat to. + * @param alias The optional alias for the chat. + * @param name The required chat name. + */ +void gaim_blist_request_add_chat(GaimAccount *account, GaimGroup *group, + const char *alias, const char *name); + +/** + * Requests from the user information needed to add a group to the + * buddy list. + */ +void gaim_blist_request_add_group(void); + +/** + * Associates a boolean with a node in the buddy list + * + * @param node The node to associate the data with + * @param key The identifier for the data + * @param value The value to set + */ +void gaim_blist_node_set_bool(GaimBlistNode *node, const char *key, gboolean value); + +/** + * Retrieves a named boolean setting from a node in the buddy list + * + * @param node The node to retrieve the data from + * @param key The identifier of the data + * + * @return The value, or FALSE if there is no setting + */ +gboolean gaim_blist_node_get_bool(GaimBlistNode *node, const char *key); + +/** + * Associates an integer with a node in the buddy list + * + * @param node The node to associate the data with + * @param key The identifier for the data + * @param value The value to set + */ +void gaim_blist_node_set_int(GaimBlistNode *node, const char *key, int value); + +/** + * Retrieves a named integer setting from a node in the buddy list + * + * @param node The node to retrieve the data from + * @param key The identifier of the data + * + * @return The value, or 0 if there is no setting + */ +int gaim_blist_node_get_int(GaimBlistNode *node, const char *key); + +/** + * Associates a string with a node in the buddy list + * + * @param node The node to associate the data with + * @param key The identifier for the data + * @param value The value to set + */ +void gaim_blist_node_set_string(GaimBlistNode *node, const char *key, + const char *value); + +/** + * Retrieves a named string setting from a node in the buddy list + * + * @param node The node to retrieve the data from + * @param key The identifier of the data + * + * @return The value, or NULL if there is no setting + */ +const char *gaim_blist_node_get_string(GaimBlistNode *node, const char *key); + +/** + * Removes a named setting from a blist node + * + * @param node The node from which to remove the setting + * @param key The name of the setting + */ +void gaim_blist_node_remove_setting(GaimBlistNode *node, const char *key); + +/** + * Set the flags for the given node. Setting a node's flags will overwrite + * the old flags, so if you want to save them, you must first call + * gaim_blist_node_get_flags and modify that appropriately. + * + * @param node The node on which to set the flags. + * @param flags The flags to set. This is a bitmask. + */ +void gaim_blist_node_set_flags(GaimBlistNode *node, GaimBlistNodeFlags flags); + +/** + * Get the current flags on a given node. + * + * @param node The node from which to get the flags. + * + * @return The flags on the node. This is a bitmask. + */ +GaimBlistNodeFlags gaim_blist_node_get_flags(GaimBlistNode *node); + +/*@}*/ + +/** + * Retrieves the extended menu items for a buddy list node. + * @param n The blist node for which to obtain the extended menu items. + * @return A list of GaimMenuAction items, as harvested by the + * blist-node-extended-menu signal. + */ +GList *gaim_blist_node_get_extended_menu(GaimBlistNode *n); + +/**************************************************************************/ +/** @name UI Registration Functions */ +/**************************************************************************/ +/*@{*/ + +/** + * Sets the UI operations structure to be used for the buddy list. + * + * @param ops The ops struct. + */ +void gaim_blist_set_ui_ops(GaimBlistUiOps *ops); + +/** + * Returns the UI operations structure to be used for the buddy list. + * + * @return The UI operations structure. + */ +GaimBlistUiOps *gaim_blist_get_ui_ops(void); + +/*@}*/ + +/**************************************************************************/ +/** @name Buddy List Subsystem */ +/**************************************************************************/ +/*@{*/ + +/** + * Returns the handle for the buddy list subsystem. + * + * @return The buddy list subsystem handle. + */ +void *gaim_blist_get_handle(void); + +/** + * Initializes the buddy list subsystem. + */ +void gaim_blist_init(void); + +/** + * Uninitializes the buddy list subsystem. + */ +void gaim_blist_uninit(void); + +/*@}*/ + +#ifdef __cplusplus +} +#endif + +#endif /* _GAIM_BLIST_H_ */ diff -r d10dda2777a9 -r b63ebf84c42b core/buddyicon.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/buddyicon.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,557 @@ +/** + * @file icon.c Buddy Icon API + * @ingroup core + * + * 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 "buddyicon.h" +#include "conversation.h" +#include "dbus-maybe.h" +#include "debug.h" +#include "util.h" + +static GHashTable *account_cache = NULL; +static char *cache_dir = NULL; +static gboolean icon_caching = TRUE; + +static GaimBuddyIcon * +gaim_buddy_icon_create(GaimAccount *account, const char *username) +{ + GaimBuddyIcon *icon; + GHashTable *icon_cache; + + icon = g_new0(GaimBuddyIcon, 1); + GAIM_DBUS_REGISTER_POINTER(icon, GaimBuddyIcon); + + gaim_buddy_icon_set_account(icon, account); + gaim_buddy_icon_set_username(icon, username); + + icon_cache = g_hash_table_lookup(account_cache, account); + + if (icon_cache == NULL) + { + icon_cache = g_hash_table_new(g_str_hash, g_str_equal); + + g_hash_table_insert(account_cache, account, icon_cache); + } + + g_hash_table_insert(icon_cache, + (char *)gaim_buddy_icon_get_username(icon), icon); + return icon; +} + +GaimBuddyIcon * +gaim_buddy_icon_new(GaimAccount *account, const char *username, + void *icon_data, size_t icon_len) +{ + GaimBuddyIcon *icon; + + g_return_val_if_fail(account != NULL, NULL); + g_return_val_if_fail(username != NULL, NULL); + g_return_val_if_fail(icon_data != NULL, NULL); + g_return_val_if_fail(icon_len > 0, NULL); + + icon = gaim_buddy_icons_find(account, username); + + if (icon == NULL) + icon = gaim_buddy_icon_create(account, username); + + gaim_buddy_icon_ref(icon); + gaim_buddy_icon_set_data(icon, icon_data, icon_len); + + /* gaim_buddy_icon_set_data() makes blist.c or + * conversation.c, or both, take a reference. + * + * Plus, we leave one for the caller of this function. + */ + + return icon; +} + +void +gaim_buddy_icon_destroy(GaimBuddyIcon *icon) +{ + GaimConversation *conv; + GaimAccount *account; + GHashTable *icon_cache; + const char *username; + GSList *sl, *list; + + g_return_if_fail(icon != NULL); + + if (icon->ref_count > 0) + { + /* If the ref count is greater than 0, then we weren't called from + * gaim_buddy_icon_unref(). So we go through and ask everyone to + * unref us. Then we return, since we know somewhere along the + * line we got called recursively by one of the unrefs, and the + * icon is already destroyed. + */ + account = gaim_buddy_icon_get_account(icon); + username = gaim_buddy_icon_get_username(icon); + + conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM, username, account); + if (conv != NULL) + gaim_conv_im_set_icon(GAIM_CONV_IM(conv), NULL); + + for (list = sl = gaim_find_buddies(account, username); sl != NULL; + sl = sl->next) + { + GaimBuddy *buddy = (GaimBuddy *)sl->data; + + gaim_buddy_set_icon(buddy, NULL); + } + + g_slist_free(list); + + return; + } + + icon_cache = g_hash_table_lookup(account_cache, + gaim_buddy_icon_get_account(icon)); + + if (icon_cache != NULL) + g_hash_table_remove(icon_cache, gaim_buddy_icon_get_username(icon)); + + g_free(icon->username); + g_free(icon->data); + GAIM_DBUS_UNREGISTER_POINTER(icon); + g_free(icon); +} + +GaimBuddyIcon * +gaim_buddy_icon_ref(GaimBuddyIcon *icon) +{ + g_return_val_if_fail(icon != NULL, NULL); + + icon->ref_count++; + + return icon; +} + +GaimBuddyIcon * +gaim_buddy_icon_unref(GaimBuddyIcon *icon) +{ + g_return_val_if_fail(icon != NULL, NULL); + g_return_val_if_fail(icon->ref_count > 0, NULL); + + icon->ref_count--; + + if (icon->ref_count == 0) + { + gaim_buddy_icon_destroy(icon); + + return NULL; + } + + return icon; +} + +void +gaim_buddy_icon_update(GaimBuddyIcon *icon) +{ + GaimConversation *conv; + GaimAccount *account; + const char *username; + GSList *sl, *list; + + g_return_if_fail(icon != NULL); + + account = gaim_buddy_icon_get_account(icon); + username = gaim_buddy_icon_get_username(icon); + + for (list = sl = gaim_find_buddies(account, username); sl != NULL; + sl = sl->next) + { + GaimBuddy *buddy = (GaimBuddy *)sl->data; + + gaim_buddy_set_icon(buddy, icon); + } + + g_slist_free(list); + + conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM, username, account); + + if (conv != NULL) + gaim_conv_im_set_icon(GAIM_CONV_IM(conv), icon); +} + +static void +delete_icon_cache_file(const char *dirname, const char *old_icon) +{ + struct stat st; + + g_return_if_fail(dirname != NULL); + g_return_if_fail(old_icon != NULL); + + if (g_stat(old_icon, &st) == 0) + g_unlink(old_icon); + else + { + char *filename = g_build_filename(dirname, old_icon, NULL); + if (g_stat(filename, &st) == 0) + g_unlink(filename); + g_free(filename); + } + gaim_debug_info("buddyicon", "Uncached file %s\n", old_icon); +} + +void +gaim_buddy_icon_cache(GaimBuddyIcon *icon, GaimBuddy *buddy) +{ + const guchar *data; + const char *dirname; + char *random; + char *filename; + const char *old_icon; + size_t len = 0; + FILE *file = NULL; + + g_return_if_fail(icon != NULL); + g_return_if_fail(buddy != NULL); + + if (!gaim_buddy_icons_is_caching()) + return; + + data = gaim_buddy_icon_get_data(icon, &len); + + random = g_strdup_printf("%x", g_random_int()); + dirname = gaim_buddy_icons_get_cache_dir(); + filename = g_build_filename(dirname, random, NULL); + old_icon = gaim_blist_node_get_string((GaimBlistNode*)buddy, "buddy_icon"); + + if (!g_file_test(dirname, G_FILE_TEST_IS_DIR)) + { + gaim_debug_info("buddyicon", "Creating icon cache directory.\n"); + + if (g_mkdir(dirname, S_IRUSR | S_IWUSR | S_IXUSR) < 0) + { + gaim_debug_error("buddyicon", + "Unable to create directory %s: %s\n", + dirname, strerror(errno)); + } + } + + if ((file = g_fopen(filename, "wb")) != NULL) + { + fwrite(data, 1, len, file); + fclose(file); + gaim_debug_info("buddyicon", "Wrote file %s\n", filename); + } + else + { + gaim_debug_error("buddyicon", "Unable to create file %s: %s\n", + filename, strerror(errno)); + } + + g_free(filename); + + if (old_icon != NULL) + delete_icon_cache_file(dirname, old_icon); + + gaim_blist_node_set_string((GaimBlistNode *)buddy, "buddy_icon", random); + + g_free(random); +} + +void +gaim_buddy_icon_uncache(GaimBuddy *buddy) +{ + const char *old_icon; + + g_return_if_fail(buddy != NULL); + + old_icon = gaim_blist_node_get_string((GaimBlistNode *)buddy, "buddy_icon"); + + if (old_icon != NULL) + delete_icon_cache_file(gaim_buddy_icons_get_cache_dir(), old_icon); + + gaim_blist_node_remove_setting((GaimBlistNode *)buddy, "buddy_icon"); + + /* Unset the icon in case this function is called from + * something other than gaim_buddy_set_icon(). */ + if (buddy->icon != NULL) + { + gaim_buddy_icon_unref(buddy->icon); + buddy->icon = NULL; + } +} + +void +gaim_buddy_icon_set_account(GaimBuddyIcon *icon, GaimAccount *account) +{ + g_return_if_fail(icon != NULL); + g_return_if_fail(account != NULL); + + icon->account = account; +} + +void +gaim_buddy_icon_set_username(GaimBuddyIcon *icon, const char *username) +{ + g_return_if_fail(icon != NULL); + g_return_if_fail(username != NULL); + + g_free(icon->username); + icon->username = g_strdup(username); +} + +void +gaim_buddy_icon_set_data(GaimBuddyIcon *icon, void *data, size_t len) +{ + g_return_if_fail(icon != NULL); + + g_free(icon->data); + + if (data != NULL && len > 0) + { + icon->data = g_memdup(data, len); + icon->len = len; + } + else + { + icon->data = NULL; + icon->len = 0; + } + + gaim_buddy_icon_update(icon); +} + +GaimAccount * +gaim_buddy_icon_get_account(const GaimBuddyIcon *icon) +{ + g_return_val_if_fail(icon != NULL, NULL); + + return icon->account; +} + +const char * +gaim_buddy_icon_get_username(const GaimBuddyIcon *icon) +{ + g_return_val_if_fail(icon != NULL, NULL); + + return icon->username; +} + +const guchar * +gaim_buddy_icon_get_data(const GaimBuddyIcon *icon, size_t *len) +{ + g_return_val_if_fail(icon != NULL, NULL); + + if (len != NULL) + *len = icon->len; + + return icon->data; +} + +const char * +gaim_buddy_icon_get_type(const GaimBuddyIcon *icon) +{ + const void *data; + size_t len; + + g_return_val_if_fail(icon != NULL, NULL); + + data = gaim_buddy_icon_get_data(icon, &len); + + /* TODO: Find a way to do this with GDK */ + if (len >= 4) + { + if (!strncmp(data, "BM", 2)) + return "bmp"; + else if (!strncmp(data, "GIF8", 4)) + return "gif"; + else if (!strncmp(data, "\xff\xd8\xff\xe0", 4)) + return "jpg"; + else if (!strncmp(data, "\x89PNG", 4)) + return "png"; + } + + return NULL; +} + +void +gaim_buddy_icons_set_for_user(GaimAccount *account, const char *username, + void *icon_data, size_t icon_len) +{ + g_return_if_fail(account != NULL); + g_return_if_fail(username != NULL); + + if (icon_data == NULL || icon_len == 0) + { + GaimBuddyIcon *buddy_icon; + + buddy_icon = gaim_buddy_icons_find(account, username); + + if (buddy_icon != NULL) + gaim_buddy_icon_destroy(buddy_icon); + } + else + { + GaimBuddyIcon *icon = gaim_buddy_icon_new(account, username, icon_data, icon_len); + gaim_buddy_icon_unref(icon); + } +} + +GaimBuddyIcon * +gaim_buddy_icons_find(GaimAccount *account, const char *username) +{ + GHashTable *icon_cache; + GaimBuddyIcon *ret = NULL; + char *filename = NULL; + + g_return_val_if_fail(account != NULL, NULL); + g_return_val_if_fail(username != NULL, NULL); + + icon_cache = g_hash_table_lookup(account_cache, account); + + if ((icon_cache == NULL) || ((ret = g_hash_table_lookup(icon_cache, username)) == NULL)) { + const char *file; + struct stat st; + GaimBuddy *b = gaim_find_buddy(account, username); + + if (!b) + return NULL; + + if ((file = gaim_blist_node_get_string((GaimBlistNode*)b, "buddy_icon")) == NULL) + return NULL; + + if (!g_stat(file, &st)) + filename = g_strdup(file); + else + filename = g_build_filename(gaim_buddy_icons_get_cache_dir(), file, NULL); + + if (!g_stat(filename, &st)) { + FILE *f = g_fopen(filename, "rb"); + if (f) { + char *data = g_malloc(st.st_size); + fread(data, 1, st.st_size, f); + fclose(f); + ret = gaim_buddy_icon_create(account, username); + gaim_buddy_icon_ref(ret); + gaim_buddy_icon_set_data(ret, data, st.st_size); + gaim_buddy_icon_unref(ret); + g_free(data); + g_free(filename); + return ret; + } + } + g_free(filename); + } + + return ret; +} + +void +gaim_buddy_icons_set_caching(gboolean caching) +{ + icon_caching = caching; +} + +gboolean +gaim_buddy_icons_is_caching(void) +{ + return icon_caching; +} + +void +gaim_buddy_icons_set_cache_dir(const char *dir) +{ + g_return_if_fail(dir != NULL); + + g_free(cache_dir); + cache_dir = g_strdup(dir); +} + +const char * +gaim_buddy_icons_get_cache_dir(void) +{ + return cache_dir; +} + +char *gaim_buddy_icons_get_full_path(const char *icon) { + struct stat st; + + if (icon == NULL) + return NULL; + + if (g_stat(icon, &st) == 0) + return g_strdup(icon); + else + return g_build_filename(gaim_buddy_icons_get_cache_dir(), icon, NULL); +} + +void * +gaim_buddy_icons_get_handle() +{ + static int handle; + + return &handle; +} + +void +gaim_buddy_icons_init() +{ + account_cache = g_hash_table_new_full( + g_direct_hash, g_direct_equal, + NULL, (GFreeFunc)g_hash_table_destroy); + + cache_dir = g_build_filename(gaim_user_dir(), "icons", NULL); +} + +void +gaim_buddy_icons_uninit() +{ + g_hash_table_destroy(account_cache); +} + +void gaim_buddy_icon_get_scale_size(GaimBuddyIconSpec *spec, int *width, int *height) +{ + if(spec && spec->scale_rules & GAIM_ICON_SCALE_DISPLAY) { + int new_width, new_height; + + new_width = *width; + new_height = *height; + + if(*width < spec->min_width) + new_width = spec->min_width; + else if(*width > spec->max_width) + new_width = spec->max_width; + + if(*height < spec->min_height) + new_height = spec->min_height; + else if(*height > spec->max_height) + new_height = spec->max_height; + + /* preserve aspect ratio */ + if ((double)*height * (double)new_width > + (double)*width * (double)new_height) { + new_width = 0.5 + (double)*width * (double)new_height / (double)*height; + } else { + new_height = 0.5 + (double)*height * (double)new_width / (double)*width; + } + + *width = new_width; + *height = new_height; + } +} + diff -r d10dda2777a9 -r b63ebf84c42b core/buddyicon.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/buddyicon.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,292 @@ +/** + * @file buddyicon.h Buddy Icon API + * @ingroup core + * + * 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 + */ +#ifndef _GAIM_BUDDYICON_H_ +#define _GAIM_BUDDYICON_H_ + +typedef struct _GaimBuddyIcon GaimBuddyIcon; + +#include "account.h" +#include "blist.h" +#include "prpl.h" + +struct _GaimBuddyIcon +{ + GaimAccount *account; /**< The account the user is on. */ + char *username; /**< The username the icon belongs to. */ + + void *data; /**< The buddy icon data. */ + size_t len; /**< The length of the buddy icon data. */ + + int ref_count; /**< The buddy icon reference count. */ +}; + +#ifdef __cplusplus +extern "C" { +#endif + +/**************************************************************************/ +/** @name Buddy Icon API */ +/**************************************************************************/ +/*@{*/ + +/** + * Creates a new buddy icon structure. + * + * @param account The account the user is on. + * @param username The username the icon belongs to. + * @param icon_data The buddy icon data. + * @param icon_len The buddy icon length. + * + * @return The buddy icon structure. + */ +GaimBuddyIcon *gaim_buddy_icon_new(GaimAccount *account, const char *username, + void *icon_data, size_t icon_len); + +/** + * Destroys a buddy icon structure. + * + * If the buddy icon's reference count is greater than 1, this will + * just decrease the reference count and return. + * + * @param icon The buddy icon structure to destroy. + */ +void gaim_buddy_icon_destroy(GaimBuddyIcon *icon); + +/** + * Increments the reference count on a buddy icon. + * + * @param icon The buddy icon. + * + * @return @a icon. + */ +GaimBuddyIcon *gaim_buddy_icon_ref(GaimBuddyIcon *icon); + +/** + * Decrements the reference count on a buddy icon. + * + * If the reference count reaches 0, the icon will be destroyed. + * + * @param icon The buddy icon. + * + * @return @a icon, or @c NULL if the reference count reached 0. + */ +GaimBuddyIcon *gaim_buddy_icon_unref(GaimBuddyIcon *icon); + +/** + * Updates every instance of this icon. + * + * @param icon The buddy icon. + */ +void gaim_buddy_icon_update(GaimBuddyIcon *icon); + +/** + * Caches a buddy icon associated with a specific buddy to disk. + * + * @param icon The buddy icon. + * @param buddy The buddy that this icon belongs to. + */ +void gaim_buddy_icon_cache(GaimBuddyIcon *icon, GaimBuddy *buddy); + +/** + * Removes cached buddy icon for a specific buddy. + * + * @param buddy The buddy for which to remove the cached icon. + */ +void gaim_buddy_icon_uncache(GaimBuddy *buddy); + +/** + * Sets the buddy icon's account. + * + * @param icon The buddy icon. + * @param account The account. + */ +void gaim_buddy_icon_set_account(GaimBuddyIcon *icon, GaimAccount *account); + +/** + * Sets the buddy icon's username. + * + * @param icon The buddy icon. + * @param username The username. + */ +void gaim_buddy_icon_set_username(GaimBuddyIcon *icon, const char *username); + +/** + * Sets the buddy icon's icon data. + * + * @param icon The buddy icon. + * @param data The buddy icon data. + * @param len The length of the icon data. + */ +void gaim_buddy_icon_set_data(GaimBuddyIcon *icon, void *data, size_t len); + +/** + * Returns the buddy icon's account. + * + * @param icon The buddy icon. + * + * @return The account. + */ +GaimAccount *gaim_buddy_icon_get_account(const GaimBuddyIcon *icon); + +/** + * Returns the buddy icon's username. + * + * @param icon The buddy icon. + * + * @return The username. + */ +const char *gaim_buddy_icon_get_username(const GaimBuddyIcon *icon); + +/** + * Returns the buddy icon's data. + * + * @param icon The buddy icon. + * @param len The returned icon length. + * + * @return The icon data. + */ +const guchar *gaim_buddy_icon_get_data(const GaimBuddyIcon *icon, size_t *len); + +/** + * Returns an extension corresponding to the buddy icon's file type. + * + * @param icon The buddy icon. + * + * @return The icon's extension. + */ +const char *gaim_buddy_icon_get_type(const GaimBuddyIcon *icon); + +/*@}*/ + +/**************************************************************************/ +/** @name Buddy Icon Subsystem API */ +/**************************************************************************/ +/*@{*/ + +/** + * Sets a buddy icon for a user. + * + * @param account The account the user is on. + * @param username The username of the user. + * @param icon_data The icon data. + * @param icon_len The length of the icon data. + */ +void gaim_buddy_icons_set_for_user(GaimAccount *account, const char *username, + void *icon_data, size_t icon_len); + +/** + * Returns the buddy icon information for a user. + * + * @param account The account the user is on. + * @param username The username of the user. + * + * @return The icon data if found, or @c NULL if not found. + */ +GaimBuddyIcon *gaim_buddy_icons_find(GaimAccount *account, + const char *username); + +/** + * Sets whether or not buddy icon caching is enabled. + * + * @param caching TRUE of buddy icon caching should be enabled, or + * FALSE otherwise. + */ +void gaim_buddy_icons_set_caching(gboolean caching); + +/** + * Returns whether or not buddy icon caching should be enabled. + * + * The default is TRUE, unless otherwise specified by + * gaim_buddy_icons_set_caching(). + * + * @return TRUE if buddy icon caching is enabled, or FALSE otherwise. + */ +gboolean gaim_buddy_icons_is_caching(void); + +/** + * Sets the directory used to store buddy icon cache files. + * + * @param cache_dir The directory to store buddy icon cache files to. + */ +void gaim_buddy_icons_set_cache_dir(const char *cache_dir); + +/** + * Returns the directory used to store buddy icon cache files. + * + * The default directory is GAIMDIR/icons, unless otherwise specified + * by gaim_buddy_icons_set_cache_dir(). + * + * @return The directory to store buddy icon cache files to. + */ +const char *gaim_buddy_icons_get_cache_dir(void); + +/** + * Takes a buddy icon and returns a full path. + * + * If @a icon is a full path to an existing file, a copy of + * @a icon is returned. Otherwise, a newly allocated string + * consiting of gaim_buddy_icons_get_cache_dir() + @a icon is + * returned. + * + * @return The full path for an icon. + */ +char *gaim_buddy_icons_get_full_path(const char *icon); + +/** + * Returns the buddy icon subsystem handle. + * + * @return The subsystem handle. + */ +void *gaim_buddy_icons_get_handle(void); + +/** + * Initializes the buddy icon subsystem. + */ +void gaim_buddy_icons_init(void); + +/** + * Uninitializes the buddy icon subsystem. + */ +void gaim_buddy_icons_uninit(void); + +/*@}*/ + +/**************************************************************************/ +/** @name Buddy Icon Helper API */ +/**************************************************************************/ +/*@{*/ + +/** + * Gets display size for a buddy icon + */ +void gaim_buddy_icon_get_scale_size(GaimBuddyIconSpec *spec, int *width, int *height); + +/*@}*/ + +#ifdef __cplusplus +} +#endif + +#endif /* _GAIM_BUDDYICON_H_ */ diff -r d10dda2777a9 -r b63ebf84c42b core/cipher.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/cipher.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,1953 @@ +/* + * 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. + * + * Original md5 + * Copyright (C) 2001-2003 Christophe Devine + * + * Original md4 taken from linux kernel + * MD4 Message Digest Algorithm (RFC1320). + * + * Implementation derived from Andrew Tridgell and Steve French's + * CIFS MD4 implementation, and the cryptoapi implementation + * originally based on the public domain implementation written + * by Colin Plumb in 1993. + * + * Copyright (c) Andrew Tridgell 1997-1998. + * Modified by Steve French (sfrench@us.ibm.com) 2002 + * Copyright (c) Cryptoapi developers. + * Copyright (c) 2002 David S. Miller (davem@redhat.com) + * Copyright (c) 2002 James Morris + * + * Original des taken from gpg + * + * des.c - DES and Triple-DES encryption/decryption Algorithm + * Copyright (C) 1998 Free Software Foundation, Inc. + * + * Please see below for more legal information! + * + * According to the definition of DES in FIPS PUB 46-2 from December 1993. + * For a description of triple encryption, see: + * Bruce Schneier: Applied Cryptography. Second Edition. + * John Wiley & Sons, 1996. ISBN 0-471-12845-7. Pages 358 ff. + * + * This file is part of GnuPG. + * + * 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 +#include +#include + +#include "internal.h" +#include "cipher.h" +#include "dbus-maybe.h" +#include "debug.h" +#include "signals.h" +#include "value.h" + +/******************************************************************************* + * MD5 + ******************************************************************************/ +struct MD5Context { + guint32 total[2]; + guint32 state[4]; + guchar buffer[64]; +}; + +#define MD5_GET_GUINT32(n,b,i) { \ + (n) = ((guint32)(b) [(i) ] ) \ + | ((guint32)(b) [(i) + 1] << 8) \ + | ((guint32)(b) [(i) + 2] << 16) \ + | ((guint32)(b) [(i) + 3] << 24); \ +} +#define MD5_PUT_GUINT32(n,b,i) { \ + (b)[(i) ] = (guchar)((n) ); \ + (b)[(i) + 1] = (guchar)((n) >> 8); \ + (b)[(i) + 2] = (guchar)((n) >> 16); \ + (b)[(i) + 3] = (guchar)((n) >> 24); \ +} + +static void +md5_init(GaimCipherContext *context, gpointer extra) { + struct MD5Context *md5_context; + + md5_context = g_new0(struct MD5Context, 1); + + gaim_cipher_context_set_data(context, md5_context); + + gaim_cipher_context_reset(context, extra); +} + +static void +md5_reset(GaimCipherContext *context, gpointer extra) { + struct MD5Context *md5_context; + + md5_context = gaim_cipher_context_get_data(context); + + md5_context->total[0] = 0; + md5_context->total[1] = 0; + + md5_context->state[0] = 0x67452301; + md5_context->state[1] = 0xEFCDAB89; + md5_context->state[2] = 0x98BADCFE; + md5_context->state[3] = 0x10325476; + + memset(md5_context->buffer, 0, sizeof(md5_context->buffer)); +} + +static void +md5_uninit(GaimCipherContext *context) { + struct MD5Context *md5_context; + + gaim_cipher_context_reset(context, NULL); + + md5_context = gaim_cipher_context_get_data(context); + memset(md5_context, 0, sizeof(md5_context)); + + g_free(md5_context); + md5_context = NULL; +} + +static void +md5_process(struct MD5Context *md5_context, const guchar data[64]) { + guint32 X[16], A, B, C, D; + + A = md5_context->state[0]; + B = md5_context->state[1]; + C = md5_context->state[2]; + D = md5_context->state[3]; + + MD5_GET_GUINT32(X[ 0], data, 0); + MD5_GET_GUINT32(X[ 1], data, 4); + MD5_GET_GUINT32(X[ 2], data, 8); + MD5_GET_GUINT32(X[ 3], data, 12); + MD5_GET_GUINT32(X[ 4], data, 16); + MD5_GET_GUINT32(X[ 5], data, 20); + MD5_GET_GUINT32(X[ 6], data, 24); + MD5_GET_GUINT32(X[ 7], data, 28); + MD5_GET_GUINT32(X[ 8], data, 32); + MD5_GET_GUINT32(X[ 9], data, 36); + MD5_GET_GUINT32(X[10], data, 40); + MD5_GET_GUINT32(X[11], data, 44); + MD5_GET_GUINT32(X[12], data, 48); + MD5_GET_GUINT32(X[13], data, 52); + MD5_GET_GUINT32(X[14], data, 56); + MD5_GET_GUINT32(X[15], data, 60); + + #define S(x,n) ((x << n) | ((x & 0xFFFFFFFF) >> (32 - n))) + #define P(a,b,c,d,k,s,t) { \ + a += F(b,c,d) + X[k] + t; \ + a = S(a,s) + b; \ + } + + /* first pass */ + #define F(x,y,z) (z ^ (x & (y ^ z))) + P(A, B, C, D, 0, 7, 0xD76AA478); + P(D, A, B, C, 1, 12, 0xE8C7B756); + P(C, D, A, B, 2, 17, 0x242070DB); + P(B, C, D, A, 3, 22, 0xC1BDCEEE); + P(A, B, C, D, 4, 7, 0xF57C0FAF); + P(D, A, B, C, 5, 12, 0x4787C62A); + P(C, D, A, B, 6, 17, 0xA8304613); + P(B, C, D, A, 7, 22, 0xFD469501); + P(A, B, C, D, 8, 7, 0x698098D8); + P(D, A, B, C, 9, 12, 0x8B44F7AF); + P(C, D, A, B, 10, 17, 0xFFFF5BB1); + P(B, C, D, A, 11, 22, 0x895CD7BE); + P(A, B, C, D, 12, 7, 0x6B901122); + P(D, A, B, C, 13, 12, 0xFD987193); + P(C, D, A, B, 14, 17, 0xA679438E); + P(B, C, D, A, 15, 22, 0x49B40821); + #undef F + + /* second pass */ + #define F(x,y,z) (y ^ (z & (x ^ y))) + P(A, B, C, D, 1, 5, 0xF61E2562); + P(D, A, B, C, 6, 9, 0xC040B340); + P(C, D, A, B, 11, 14, 0x265E5A51); + P(B, C, D, A, 0, 20, 0xE9B6C7AA); + P(A, B, C, D, 5, 5, 0xD62F105D); + P(D, A, B, C, 10, 9, 0x02441453); + P(C, D, A, B, 15, 14, 0xD8A1E681); + P(B, C, D, A, 4, 20, 0xE7D3FBC8); + P(A, B, C, D, 9, 5, 0x21E1CDE6); + P(D, A, B, C, 14, 9, 0xC33707D6); + P(C, D, A, B, 3, 14, 0xF4D50D87); + P(B, C, D, A, 8, 20, 0x455A14ED); + P(A, B, C, D, 13, 5, 0xA9E3E905); + P(D, A, B, C, 2, 9, 0xFCEFA3F8); + P(C, D, A, B, 7, 14, 0x676F02D9); + P(B, C, D, A, 12, 20, 0x8D2A4C8A); + #undef F + + /* third pass */ + #define F(x,y,z) (x ^ y ^ z) + P(A, B, C, D, 5, 4, 0xFFFA3942); + P(D, A, B, C, 8, 11, 0x8771F681); + P(C, D, A, B, 11, 16, 0x6D9D6122); + P(B, C, D, A, 14, 23, 0xFDE5380C); + P(A, B, C, D, 1, 4, 0xA4BEEA44); + P(D, A, B, C, 4, 11, 0x4BDECFA9); + P(C, D, A, B, 7, 16, 0xF6BB4B60); + P(B, C, D, A, 10, 23, 0xBEBFBC70); + P(A, B, C, D, 13, 4, 0x289B7EC6); + P(D, A, B, C, 0, 11, 0xEAA127FA); + P(C, D, A, B, 3, 16, 0xD4EF3085); + P(B, C, D, A, 6, 23, 0x04881D05); + P(A, B, C, D, 9, 4, 0xD9D4D039); + P(D, A, B, C, 12, 11, 0xE6DB99E5); + P(C, D, A, B, 15, 16, 0x1FA27CF8); + P(B, C, D, A, 2, 23, 0xC4AC5665); + #undef F + + /* forth pass */ + #define F(x,y,z) (y ^ (x | ~z)) + P(A, B, C, D, 0, 6, 0xF4292244); + P(D, A, B, C, 7, 10, 0x432AFF97); + P(C, D, A, B, 14, 15, 0xAB9423A7); + P(B, C, D, A, 5, 21, 0xFC93A039); + P(A, B, C, D, 12, 6, 0x655B59C3); + P(D, A, B, C, 3, 10, 0x8F0CCC92); + P(C, D, A, B, 10, 15, 0xFFEFF47D); + P(B, C, D, A, 1, 21, 0x85845DD1); + P(A, B, C, D, 8, 6, 0x6FA87E4F); + P(D, A, B, C, 15, 10, 0xFE2CE6E0); + P(C, D, A, B, 6, 15, 0xA3014314); + P(B, C, D, A, 13, 21, 0x4E0811A1); + P(A, B, C, D, 4, 6, 0xF7537E82); + P(D, A, B, C, 11, 10, 0xBD3AF235); + P(C, D, A, B, 2, 15, 0x2AD7D2BB); + P(B, C, D, A, 9, 21, 0xEB86D391); + #undef F + #undef P + #undef S + + md5_context->state[0] += A; + md5_context->state[1] += B; + md5_context->state[2] += C; + md5_context->state[3] += D; +} + +static void +md5_append(GaimCipherContext *context, const guchar *data, size_t len) { + struct MD5Context *md5_context = NULL; + guint32 left = 0, fill = 0; + + g_return_if_fail(context != NULL); + + md5_context = gaim_cipher_context_get_data(context); + g_return_if_fail(md5_context != NULL); + + left = md5_context->total[0] & 0x3F; + fill = 64 - left; + + md5_context->total[0] += len; + md5_context->total[0] &= 0xFFFFFFFF; + + if(md5_context->total[0] < len) + md5_context->total[1]++; + + if(left && len >= fill) { + memcpy((md5_context->buffer + left), data, fill); + md5_process(md5_context, md5_context->buffer); + len -= fill; + data += fill; + left = 0; + } + + while(len >= 64) { + md5_process(md5_context, data); + len -= 64; + data += 64; + } + + if(len) { + memcpy((md5_context->buffer + left), data, len); + } +} + +static gboolean +md5_digest(GaimCipherContext *context, size_t in_len, guchar digest[16], + size_t *out_len) +{ + struct MD5Context *md5_context = NULL; + guint32 last, pad; + guint32 high, low; + guchar message[8]; + guchar padding[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + + g_return_val_if_fail(in_len >= 16, FALSE); + + md5_context = gaim_cipher_context_get_data(context); + + high = (md5_context->total[0] >> 29) + | (md5_context->total[1] << 3); + low = (md5_context->total[0] << 3); + + MD5_PUT_GUINT32(low, message, 0); + MD5_PUT_GUINT32(high, message, 4); + + last = md5_context->total[0] & 0x3F; + pad = (last < 56) ? (56 - last) : (120 - last); + + md5_append(context, padding, pad); + md5_append(context, message, 8); + + MD5_PUT_GUINT32(md5_context->state[0], digest, 0); + MD5_PUT_GUINT32(md5_context->state[1], digest, 4); + MD5_PUT_GUINT32(md5_context->state[2], digest, 8); + MD5_PUT_GUINT32(md5_context->state[3], digest, 12); + + if(out_len) + *out_len = 16; + + return TRUE; +} + +static GaimCipherOps MD5Ops = { + NULL, /* Set option */ + NULL, /* Get option */ + md5_init, /* init */ + md5_reset, /* reset */ + md5_uninit, /* uninit */ + NULL, /* set iv */ + md5_append, /* append */ + md5_digest, /* digest */ + NULL, /* encrypt */ + NULL, /* decrypt */ + NULL, /* set salt */ + NULL, /* get salt size */ + NULL, /* set key */ + NULL /* get key size */ +}; + +/******************************************************************************* + * MD4 + ******************************************************************************/ +#define MD4_DIGEST_SIZE 16 +#define MD4_HMAC_BLOCK_SIZE 64 +#define MD4_BLOCK_WORDS 16 +#define MD4_HASH_WORDS 4 + + + +struct MD4_Context { + guint32 hash[MD4_HASH_WORDS]; + guint32 block[MD4_BLOCK_WORDS]; + guint64 byte_count; +}; + +static inline guint32 lshift(guint32 x, unsigned int s) +{ + x &= 0xFFFFFFFF; + return ((x << s) & 0xFFFFFFFF) | (x >> (32 - s)); +} + +static inline guint32 F(guint32 x, guint32 y, guint32 z) +{ + return (x & y) | ((~x) & z); +} + +static inline guint32 G(guint32 x, guint32 y, guint32 z) +{ + return (x & y) | (x & z) | (y & z); +} + +static inline guint32 H(guint32 x, guint32 y, guint32 z) +{ + return x ^ y ^ z; +} + +#define ROUND1(a,b,c,d,k,s) (a = lshift(a + F(b,c,d) + k, s)) +#define ROUND2(a,b,c,d,k,s) (a = lshift(a + G(b,c,d) + k + (guint32)0x5A827999,s)) +#define ROUND3(a,b,c,d,k,s) (a = lshift(a + H(b,c,d) + k + (guint32)0x6ED9EBA1,s)) + +static inline void le32_to_cpu_array(guint32 *buf, unsigned int words) +{ + while (words--) { + *buf=GUINT_FROM_LE(*buf); + buf++; + } +} + +static inline void cpu_to_le32_array(guint32 *buf, unsigned int words) +{ + while (words--) { + *buf=GUINT_TO_LE(*buf); + buf++; + } +} + +static void md4_transform(guint32 *hash, guint32 const *in) +{ + guint32 a, b, c, d; + + a = hash[0]; + b = hash[1]; + c = hash[2]; + d = hash[3]; + + ROUND1(a, b, c, d, in[0], 3); + ROUND1(d, a, b, c, in[1], 7); + ROUND1(c, d, a, b, in[2], 11); + ROUND1(b, c, d, a, in[3], 19); + ROUND1(a, b, c, d, in[4], 3); + ROUND1(d, a, b, c, in[5], 7); + ROUND1(c, d, a, b, in[6], 11); + ROUND1(b, c, d, a, in[7], 19); + ROUND1(a, b, c, d, in[8], 3); + ROUND1(d, a, b, c, in[9], 7); + ROUND1(c, d, a, b, in[10], 11); + ROUND1(b, c, d, a, in[11], 19); + ROUND1(a, b, c, d, in[12], 3); + ROUND1(d, a, b, c, in[13], 7); + ROUND1(c, d, a, b, in[14], 11); + ROUND1(b, c, d, a, in[15], 19); + + ROUND2(a, b, c, d,in[ 0], 3); + ROUND2(d, a, b, c, in[4], 5); + ROUND2(c, d, a, b, in[8], 9); + ROUND2(b, c, d, a, in[12], 13); + ROUND2(a, b, c, d, in[1], 3); + ROUND2(d, a, b, c, in[5], 5); + ROUND2(c, d, a, b, in[9], 9); + ROUND2(b, c, d, a, in[13], 13); + ROUND2(a, b, c, d, in[2], 3); + ROUND2(d, a, b, c, in[6], 5); + ROUND2(c, d, a, b, in[10], 9); + ROUND2(b, c, d, a, in[14], 13); + ROUND2(a, b, c, d, in[3], 3); + ROUND2(d, a, b, c, in[7], 5); + ROUND2(c, d, a, b, in[11], 9); + ROUND2(b, c, d, a, in[15], 13); + + ROUND3(a, b, c, d,in[ 0], 3); + ROUND3(d, a, b, c, in[8], 9); + ROUND3(c, d, a, b, in[4], 11); + ROUND3(b, c, d, a, in[12], 15); + ROUND3(a, b, c, d, in[2], 3); + ROUND3(d, a, b, c, in[10], 9); + ROUND3(c, d, a, b, in[6], 11); + ROUND3(b, c, d, a, in[14], 15); + ROUND3(a, b, c, d, in[1], 3); + ROUND3(d, a, b, c, in[9], 9); + ROUND3(c, d, a, b, in[5], 11); + ROUND3(b, c, d, a, in[13], 15); + ROUND3(a, b, c, d, in[3], 3); + ROUND3(d, a, b, c, in[11], 9); + ROUND3(c, d, a, b, in[7], 11); + ROUND3(b, c, d, a, in[15], 15); + + hash[0] += a; + hash[1] += b; + hash[2] += c; + hash[3] += d; +} + +static inline void md4_transform_helper(struct MD4_Context *ctx) +{ + le32_to_cpu_array(ctx->block, sizeof(ctx->block) / sizeof(guint32)); + md4_transform(ctx->hash, ctx->block); +} + +static void +md4_init(GaimCipherContext *context, gpointer extra) { + struct MD4_Context *mctx; + mctx = g_new0(struct MD4_Context, 1); + gaim_cipher_context_set_data(context, mctx); + gaim_cipher_context_reset(context, extra); + + mctx->hash[0] = 0x67452301; + mctx->hash[1] = 0xefcdab89; + mctx->hash[2] = 0x98badcfe; + mctx->hash[3] = 0x10325476; + mctx->byte_count = 0; +} + +static void +md4_reset(GaimCipherContext *context, gpointer extra) { + struct MD4_Context *mctx; + + mctx = gaim_cipher_context_get_data(context); + + mctx->hash[0] = 0x67452301; + mctx->hash[1] = 0xefcdab89; + mctx->hash[2] = 0x98badcfe; + mctx->hash[3] = 0x10325476; + mctx->byte_count = 0; +} + +static void +md4_append(GaimCipherContext *context, const guchar *data, size_t len) +{ + struct MD4_Context *mctx = gaim_cipher_context_get_data(context); + const guint32 avail = sizeof(mctx->block) - (mctx->byte_count & 0x3f); + + mctx->byte_count += len; + + if (avail > len) { + memcpy((char *)mctx->block + (sizeof(mctx->block) - avail), + data, len); + return; + } + + memcpy((char *)mctx->block + (sizeof(mctx->block) - avail), + data, avail); + + md4_transform_helper(mctx); + data += avail; + len -= avail; + + while (len >= sizeof(mctx->block)) { + memcpy(mctx->block, data, sizeof(mctx->block)); + md4_transform_helper(mctx); + data += sizeof(mctx->block); + len -= sizeof(mctx->block); + } + + memcpy(mctx->block, data, len); +} + +static gboolean +md4_digest(GaimCipherContext *context, size_t in_len, guchar *out, + size_t *out_len) +{ + struct MD4_Context *mctx = gaim_cipher_context_get_data(context); + const unsigned int offset = mctx->byte_count & 0x3f; + char *p = (char *)mctx->block + offset; + int padding = 56 - (offset + 1); + + + if(in_len<16) return FALSE; + if(out_len) *out_len = 16; + *p++ = 0x80; + if (padding < 0) { + memset(p, 0x00, padding + sizeof (guint64)); + md4_transform_helper(mctx); + p = (char *)mctx->block; + padding = 56; + } + + memset(p, 0, padding); + mctx->block[14] = mctx->byte_count << 3; + mctx->block[15] = mctx->byte_count >> 29; + le32_to_cpu_array(mctx->block, (sizeof(mctx->block) - + sizeof(guint64)) / sizeof(guint32)); + md4_transform(mctx->hash, mctx->block); + cpu_to_le32_array(mctx->hash, sizeof(mctx->hash) / sizeof(guint32)); + memcpy(out, mctx->hash, sizeof(mctx->hash)); + memset(mctx, 0, sizeof(*mctx)); + return TRUE; +} + +static void +md4_uninit(GaimCipherContext *context) { + struct MD4_Context *md4_context; + + gaim_cipher_context_reset(context, NULL); + + md4_context = gaim_cipher_context_get_data(context); + memset(md4_context, 0, sizeof(md4_context)); + + g_free(md4_context); + md4_context = NULL; +} + +static GaimCipherOps MD4Ops = { + NULL, /* Set option */ + NULL, /* Get option */ + md4_init, /* init */ + md4_reset, /* reset */ + md4_uninit, /* uninit */ + NULL, /* set iv */ + md4_append, /* append */ + md4_digest, /* digest */ + NULL, /* encrypt */ + NULL, /* decrypt */ + NULL, /* set salt */ + NULL, /* get salt size */ + NULL, /* set key */ + NULL /* get key size */ +}; + +/****************************************************************************** + * DES + *****************************************************************************/ + +typedef struct _des_ctx +{ + guint32 encrypt_subkeys[32]; + guint32 decrypt_subkeys[32]; +} des_ctx[1]; + +/* + * The s-box values are permuted according to the 'primitive function P' + */ +static guint32 sbox1[64] = +{ + 0x00808200, 0x00000000, 0x00008000, 0x00808202, 0x00808002, 0x00008202, 0x00000002, 0x00008000, + 0x00000200, 0x00808200, 0x00808202, 0x00000200, 0x00800202, 0x00808002, 0x00800000, 0x00000002, + 0x00000202, 0x00800200, 0x00800200, 0x00008200, 0x00008200, 0x00808000, 0x00808000, 0x00800202, + 0x00008002, 0x00800002, 0x00800002, 0x00008002, 0x00000000, 0x00000202, 0x00008202, 0x00800000, + 0x00008000, 0x00808202, 0x00000002, 0x00808000, 0x00808200, 0x00800000, 0x00800000, 0x00000200, + 0x00808002, 0x00008000, 0x00008200, 0x00800002, 0x00000200, 0x00000002, 0x00800202, 0x00008202, + 0x00808202, 0x00008002, 0x00808000, 0x00800202, 0x00800002, 0x00000202, 0x00008202, 0x00808200, + 0x00000202, 0x00800200, 0x00800200, 0x00000000, 0x00008002, 0x00008200, 0x00000000, 0x00808002 +}; + +static guint32 sbox2[64] = +{ + 0x40084010, 0x40004000, 0x00004000, 0x00084010, 0x00080000, 0x00000010, 0x40080010, 0x40004010, + 0x40000010, 0x40084010, 0x40084000, 0x40000000, 0x40004000, 0x00080000, 0x00000010, 0x40080010, + 0x00084000, 0x00080010, 0x40004010, 0x00000000, 0x40000000, 0x00004000, 0x00084010, 0x40080000, + 0x00080010, 0x40000010, 0x00000000, 0x00084000, 0x00004010, 0x40084000, 0x40080000, 0x00004010, + 0x00000000, 0x00084010, 0x40080010, 0x00080000, 0x40004010, 0x40080000, 0x40084000, 0x00004000, + 0x40080000, 0x40004000, 0x00000010, 0x40084010, 0x00084010, 0x00000010, 0x00004000, 0x40000000, + 0x00004010, 0x40084000, 0x00080000, 0x40000010, 0x00080010, 0x40004010, 0x40000010, 0x00080010, + 0x00084000, 0x00000000, 0x40004000, 0x00004010, 0x40000000, 0x40080010, 0x40084010, 0x00084000 +}; + +static guint32 sbox3[64] = +{ + 0x00000104, 0x04010100, 0x00000000, 0x04010004, 0x04000100, 0x00000000, 0x00010104, 0x04000100, + 0x00010004, 0x04000004, 0x04000004, 0x00010000, 0x04010104, 0x00010004, 0x04010000, 0x00000104, + 0x04000000, 0x00000004, 0x04010100, 0x00000100, 0x00010100, 0x04010000, 0x04010004, 0x00010104, + 0x04000104, 0x00010100, 0x00010000, 0x04000104, 0x00000004, 0x04010104, 0x00000100, 0x04000000, + 0x04010100, 0x04000000, 0x00010004, 0x00000104, 0x00010000, 0x04010100, 0x04000100, 0x00000000, + 0x00000100, 0x00010004, 0x04010104, 0x04000100, 0x04000004, 0x00000100, 0x00000000, 0x04010004, + 0x04000104, 0x00010000, 0x04000000, 0x04010104, 0x00000004, 0x00010104, 0x00010100, 0x04000004, + 0x04010000, 0x04000104, 0x00000104, 0x04010000, 0x00010104, 0x00000004, 0x04010004, 0x00010100 +}; + +static guint32 sbox4[64] = +{ + 0x80401000, 0x80001040, 0x80001040, 0x00000040, 0x00401040, 0x80400040, 0x80400000, 0x80001000, + 0x00000000, 0x00401000, 0x00401000, 0x80401040, 0x80000040, 0x00000000, 0x00400040, 0x80400000, + 0x80000000, 0x00001000, 0x00400000, 0x80401000, 0x00000040, 0x00400000, 0x80001000, 0x00001040, + 0x80400040, 0x80000000, 0x00001040, 0x00400040, 0x00001000, 0x00401040, 0x80401040, 0x80000040, + 0x00400040, 0x80400000, 0x00401000, 0x80401040, 0x80000040, 0x00000000, 0x00000000, 0x00401000, + 0x00001040, 0x00400040, 0x80400040, 0x80000000, 0x80401000, 0x80001040, 0x80001040, 0x00000040, + 0x80401040, 0x80000040, 0x80000000, 0x00001000, 0x80400000, 0x80001000, 0x00401040, 0x80400040, + 0x80001000, 0x00001040, 0x00400000, 0x80401000, 0x00000040, 0x00400000, 0x00001000, 0x00401040 +}; + +static guint32 sbox5[64] = +{ + 0x00000080, 0x01040080, 0x01040000, 0x21000080, 0x00040000, 0x00000080, 0x20000000, 0x01040000, + 0x20040080, 0x00040000, 0x01000080, 0x20040080, 0x21000080, 0x21040000, 0x00040080, 0x20000000, + 0x01000000, 0x20040000, 0x20040000, 0x00000000, 0x20000080, 0x21040080, 0x21040080, 0x01000080, + 0x21040000, 0x20000080, 0x00000000, 0x21000000, 0x01040080, 0x01000000, 0x21000000, 0x00040080, + 0x00040000, 0x21000080, 0x00000080, 0x01000000, 0x20000000, 0x01040000, 0x21000080, 0x20040080, + 0x01000080, 0x20000000, 0x21040000, 0x01040080, 0x20040080, 0x00000080, 0x01000000, 0x21040000, + 0x21040080, 0x00040080, 0x21000000, 0x21040080, 0x01040000, 0x00000000, 0x20040000, 0x21000000, + 0x00040080, 0x01000080, 0x20000080, 0x00040000, 0x00000000, 0x20040000, 0x01040080, 0x20000080 +}; + +static guint32 sbox6[64] = +{ + 0x10000008, 0x10200000, 0x00002000, 0x10202008, 0x10200000, 0x00000008, 0x10202008, 0x00200000, + 0x10002000, 0x00202008, 0x00200000, 0x10000008, 0x00200008, 0x10002000, 0x10000000, 0x00002008, + 0x00000000, 0x00200008, 0x10002008, 0x00002000, 0x00202000, 0x10002008, 0x00000008, 0x10200008, + 0x10200008, 0x00000000, 0x00202008, 0x10202000, 0x00002008, 0x00202000, 0x10202000, 0x10000000, + 0x10002000, 0x00000008, 0x10200008, 0x00202000, 0x10202008, 0x00200000, 0x00002008, 0x10000008, + 0x00200000, 0x10002000, 0x10000000, 0x00002008, 0x10000008, 0x10202008, 0x00202000, 0x10200000, + 0x00202008, 0x10202000, 0x00000000, 0x10200008, 0x00000008, 0x00002000, 0x10200000, 0x00202008, + 0x00002000, 0x00200008, 0x10002008, 0x00000000, 0x10202000, 0x10000000, 0x00200008, 0x10002008 +}; + +static guint32 sbox7[64] = +{ + 0x00100000, 0x02100001, 0x02000401, 0x00000000, 0x00000400, 0x02000401, 0x00100401, 0x02100400, + 0x02100401, 0x00100000, 0x00000000, 0x02000001, 0x00000001, 0x02000000, 0x02100001, 0x00000401, + 0x02000400, 0x00100401, 0x00100001, 0x02000400, 0x02000001, 0x02100000, 0x02100400, 0x00100001, + 0x02100000, 0x00000400, 0x00000401, 0x02100401, 0x00100400, 0x00000001, 0x02000000, 0x00100400, + 0x02000000, 0x00100400, 0x00100000, 0x02000401, 0x02000401, 0x02100001, 0x02100001, 0x00000001, + 0x00100001, 0x02000000, 0x02000400, 0x00100000, 0x02100400, 0x00000401, 0x00100401, 0x02100400, + 0x00000401, 0x02000001, 0x02100401, 0x02100000, 0x00100400, 0x00000000, 0x00000001, 0x02100401, + 0x00000000, 0x00100401, 0x02100000, 0x00000400, 0x02000001, 0x02000400, 0x00000400, 0x00100001 +}; + +static guint32 sbox8[64] = +{ + 0x08000820, 0x00000800, 0x00020000, 0x08020820, 0x08000000, 0x08000820, 0x00000020, 0x08000000, + 0x00020020, 0x08020000, 0x08020820, 0x00020800, 0x08020800, 0x00020820, 0x00000800, 0x00000020, + 0x08020000, 0x08000020, 0x08000800, 0x00000820, 0x00020800, 0x00020020, 0x08020020, 0x08020800, + 0x00000820, 0x00000000, 0x00000000, 0x08020020, 0x08000020, 0x08000800, 0x00020820, 0x00020000, + 0x00020820, 0x00020000, 0x08020800, 0x00000800, 0x00000020, 0x08020020, 0x00000800, 0x00020820, + 0x08000800, 0x00000020, 0x08000020, 0x08020000, 0x08020020, 0x08000000, 0x00020000, 0x08000820, + 0x00000000, 0x08020820, 0x00020020, 0x08000020, 0x08020000, 0x08000800, 0x08000820, 0x00000000, + 0x08020820, 0x00020800, 0x00020800, 0x00000820, 0x00000820, 0x00020020, 0x08000000, 0x08020800 +}; + + + +/* + * * These two tables are part of the 'permuted choice 1' function. + * * In this implementation several speed improvements are done. + * */ +static guint32 leftkey_swap[16] = +{ + 0x00000000, 0x00000001, 0x00000100, 0x00000101, + 0x00010000, 0x00010001, 0x00010100, 0x00010101, + 0x01000000, 0x01000001, 0x01000100, 0x01000101, + 0x01010000, 0x01010001, 0x01010100, 0x01010101 +}; + +static guint32 rightkey_swap[16] = +{ + 0x00000000, 0x01000000, 0x00010000, 0x01010000, + 0x00000100, 0x01000100, 0x00010100, 0x01010100, + 0x00000001, 0x01000001, 0x00010001, 0x01010001, + 0x00000101, 0x01000101, 0x00010101, 0x01010101, +}; + + + +/* + * Numbers of left shifts per round for encryption subkey schedule + * To calculate the decryption key scheduling we just reverse the + * ordering of the subkeys so we can omit the table for decryption + * subkey schedule. + */ +static guint8 encrypt_rotate_tab[16] = +{ + 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 +}; + +/* + * Macro to swap bits across two words + **/ +#define DO_PERMUTATION(a, temp, b, offset, mask) \ + temp = ((a>>offset) ^ b) & mask; \ +b ^= temp; \ +a ^= temp<>31)) ^ *subkey++; \ +to ^= sbox8[ work & 0x3f ]; \ +to ^= sbox6[ (work>>8) & 0x3f ]; \ +to ^= sbox4[ (work>>16) & 0x3f ]; \ +to ^= sbox2[ (work>>24) & 0x3f ]; \ +work = ((from>>3) | (from<<29)) ^ *subkey++; \ +to ^= sbox7[ work & 0x3f ]; \ +to ^= sbox5[ (work>>8) & 0x3f ]; \ +to ^= sbox3[ (work>>16) & 0x3f ]; \ +to ^= sbox1[ (work>>24) & 0x3f ]; + + +/* + * Macros to convert 8 bytes from/to 32bit words + **/ +#define READ_64BIT_DATA(data, left, right) \ + left = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3]; \ +right = (data[4] << 24) | (data[5] << 16) | (data[6] << 8) | data[7]; + +#define WRITE_64BIT_DATA(data, left, right) \ + data[0] = (left >> 24) &0xff; data[1] = (left >> 16) &0xff; \ +data[2] = (left >> 8) &0xff; data[3] = left &0xff; \ +data[4] = (right >> 24) &0xff; data[5] = (right >> 16) &0xff; \ +data[6] = (right >> 8) &0xff; data[7] = right &0xff; + + + + + + +/* + * des_key_schedule(): Calculate 16 subkeys pairs (even/odd) for + * 16 encryption rounds. + * To calculate subkeys for decryption the caller + * have to reorder the generated subkeys. + * + * rawkey: 8 Bytes of key data + * subkey: Array of at least 32 guint32s. Will be filled + * with calculated subkeys. + * + **/ +static void +des_key_schedule (const guint8 * rawkey, guint32 * subkey) +{ + guint32 left, right, work; + int round; + + READ_64BIT_DATA (rawkey, left, right) + + DO_PERMUTATION (right, work, left, 4, 0x0f0f0f0f) + DO_PERMUTATION (right, work, left, 0, 0x10101010) + + left = (leftkey_swap[(left >> 0) & 0xf] << 3) | (leftkey_swap[(left >> 8) & 0xf] << 2) + | (leftkey_swap[(left >> 16) & 0xf] << 1) | (leftkey_swap[(left >> 24) & 0xf]) + | (leftkey_swap[(left >> 5) & 0xf] << 7) | (leftkey_swap[(left >> 13) & 0xf] << 6) + | (leftkey_swap[(left >> 21) & 0xf] << 5) | (leftkey_swap[(left >> 29) & 0xf] << 4); + + left &= 0x0fffffff; + + right = (rightkey_swap[(right >> 1) & 0xf] << 3) | (rightkey_swap[(right >> 9) & 0xf] << 2) + | (rightkey_swap[(right >> 17) & 0xf] << 1) | (rightkey_swap[(right >> 25) & 0xf]) + | (rightkey_swap[(right >> 4) & 0xf] << 7) | (rightkey_swap[(right >> 12) & 0xf] << 6) + | (rightkey_swap[(right >> 20) & 0xf] << 5) | (rightkey_swap[(right >> 28) & 0xf] << 4); + + right &= 0x0fffffff; + + for (round = 0; round < 16; ++round) + { + left = ((left << encrypt_rotate_tab[round]) | (left >> (28 - encrypt_rotate_tab[round]))) & 0x0fffffff; + right = ((right << encrypt_rotate_tab[round]) | (right >> (28 - encrypt_rotate_tab[round]))) & 0x0fffffff; + + *subkey++ = ((left << 4) & 0x24000000) + | ((left << 28) & 0x10000000) + | ((left << 14) & 0x08000000) + | ((left << 18) & 0x02080000) + | ((left << 6) & 0x01000000) + | ((left << 9) & 0x00200000) + | ((left >> 1) & 0x00100000) + | ((left << 10) & 0x00040000) + | ((left << 2) & 0x00020000) + | ((left >> 10) & 0x00010000) + | ((right >> 13) & 0x00002000) + | ((right >> 4) & 0x00001000) + | ((right << 6) & 0x00000800) + | ((right >> 1) & 0x00000400) + | ((right >> 14) & 0x00000200) + | (right & 0x00000100) + | ((right >> 5) & 0x00000020) + | ((right >> 10) & 0x00000010) + | ((right >> 3) & 0x00000008) + | ((right >> 18) & 0x00000004) + | ((right >> 26) & 0x00000002) + | ((right >> 24) & 0x00000001); + + *subkey++ = ((left << 15) & 0x20000000) + | ((left << 17) & 0x10000000) + | ((left << 10) & 0x08000000) + | ((left << 22) & 0x04000000) + | ((left >> 2) & 0x02000000) + | ((left << 1) & 0x01000000) + | ((left << 16) & 0x00200000) + | ((left << 11) & 0x00100000) + | ((left << 3) & 0x00080000) + | ((left >> 6) & 0x00040000) + | ((left << 15) & 0x00020000) + | ((left >> 4) & 0x00010000) + | ((right >> 2) & 0x00002000) + | ((right << 8) & 0x00001000) + | ((right >> 14) & 0x00000808) + | ((right >> 9) & 0x00000400) + | ((right) & 0x00000200) + | ((right << 7) & 0x00000100) + | ((right >> 7) & 0x00000020) + | ((right >> 3) & 0x00000011) + | ((right << 2) & 0x00000004) + | ((right >> 21) & 0x00000002); + } +} + + + +/* + * Fill a DES context with subkeys calculated from a 64bit key. + * Does not check parity bits, but simply ignore them. + * Does not check for weak keys. + **/ +static void +des_set_key (GaimCipherContext *context, const guchar * key) +{ + struct _des_ctx *ctx = gaim_cipher_context_get_data(context); + int i; + + des_key_schedule (key, ctx->encrypt_subkeys); + + for(i=0; i<32; i+=2) + { + ctx->decrypt_subkeys[i] = ctx->encrypt_subkeys[30-i]; + ctx->decrypt_subkeys[i+1] = ctx->encrypt_subkeys[31-i]; + } +} + + + +/* + * Electronic Codebook Mode DES encryption/decryption of data according + * to 'mode'. + **/ +static int +des_ecb_crypt (struct _des_ctx *ctx, const guint8 * from, guint8 * to, int mode) +{ + guint32 left, right, work; + guint32 *keys; + + keys = mode ? ctx->decrypt_subkeys : ctx->encrypt_subkeys; + + READ_64BIT_DATA (from, left, right) + INITIAL_PERMUTATION (left, work, right) + + DES_ROUND (right, left, work, keys) DES_ROUND (left, right, work, keys) + DES_ROUND (right, left, work, keys) DES_ROUND (left, right, work, keys) + DES_ROUND (right, left, work, keys) DES_ROUND (left, right, work, keys) + DES_ROUND (right, left, work, keys) DES_ROUND (left, right, work, keys) + DES_ROUND (right, left, work, keys) DES_ROUND (left, right, work, keys) + DES_ROUND (right, left, work, keys) DES_ROUND (left, right, work, keys) + DES_ROUND (right, left, work, keys) DES_ROUND (left, right, work, keys) + DES_ROUND (right, left, work, keys) DES_ROUND (left, right, work, keys) + + FINAL_PERMUTATION (right, work, left) + WRITE_64BIT_DATA (to, right, left) + + return 0; +} + +static gint +des_encrypt(GaimCipherContext *context, const guchar data[], + size_t len, guchar output[], size_t *outlen) { + int offset = 0; + int i = 0; + int tmp; + guint8 buf[8] = {0,0,0,0,0,0,0,0}; + while(offset+8<=len) { + des_ecb_crypt(gaim_cipher_context_get_data(context), + data+offset, + output+offset, + 0); + offset+=8; + } + *outlen = len; + if(offset> (32-(n)))) & 0xFFFFFFFF) + +struct SHA1Context { + guint32 H[5]; + guint32 W[80]; + + gint lenW; + + guint32 sizeHi; + guint32 sizeLo; +}; + +static void +sha1_hash_block(struct SHA1Context *sha1_ctx) { + gint i; + guint32 A, B, C, D, E, T; + + for(i = 16; i < 80; i++) { + sha1_ctx->W[i] = SHA1_ROTL(sha1_ctx->W[i - 3] ^ + sha1_ctx->W[i - 8] ^ + sha1_ctx->W[i - 14] ^ + sha1_ctx->W[i - 16], 1); + } + + A = sha1_ctx->H[0]; + B = sha1_ctx->H[1]; + C = sha1_ctx->H[2]; + D = sha1_ctx->H[3]; + E = sha1_ctx->H[4]; + + for(i = 0; i < 20; i++) { + T = (SHA1_ROTL(A, 5) + (((C ^ D) & B) ^ D) + E + sha1_ctx->W[i] + 0x5A827999) & 0xFFFFFFFF; + E = D; + D = C; + C = SHA1_ROTL(B, 30); + B = A; + A = T; + } + + for(i = 20; i < 40; i++) { + T = (SHA1_ROTL(A, 5) + (B ^ C ^ D) + E + sha1_ctx->W[i] + 0x6ED9EBA1) & 0xFFFFFFFF; + E = D; + D = C; + C = SHA1_ROTL(B, 30); + B = A; + A = T; + } + + for(i = 40; i < 60; i++) { + T = (SHA1_ROTL(A, 5) + ((B & C) | (D & (B | C))) + E + sha1_ctx->W[i] + 0x8F1BBCDC) & 0xFFFFFFFF; + E = D; + D = C; + C = SHA1_ROTL(B, 30); + B = A; + A = T; + } + + for(i = 60; i < 80; i++) { + T = (SHA1_ROTL(A, 5) + (B ^ C ^ D) + E + sha1_ctx->W[i] + 0xCA62C1D6) & 0xFFFFFFFF; + E = D; + D = C; + C = SHA1_ROTL(B, 30); + B = A; + A = T; + } + + sha1_ctx->H[0] += A; + sha1_ctx->H[1] += B; + sha1_ctx->H[2] += C; + sha1_ctx->H[3] += D; + sha1_ctx->H[4] += E; +} + +static void +sha1_set_opt(GaimCipherContext *context, const gchar *name, void *value) { + struct SHA1Context *ctx; + + ctx = gaim_cipher_context_get_data(context); + + if(!strcmp(name, "sizeHi")) { + ctx->sizeHi = GPOINTER_TO_INT(value); + } else if(!strcmp(name, "sizeLo")) { + ctx->sizeLo = GPOINTER_TO_INT(value); + } else if(!strcmp(name, "lenW")) { + ctx->lenW = GPOINTER_TO_INT(value); + } +} + +static void * +sha1_get_opt(GaimCipherContext *context, const gchar *name) { + struct SHA1Context *ctx; + + ctx = gaim_cipher_context_get_data(context); + + if(!strcmp(name, "sizeHi")) { + return GINT_TO_POINTER(ctx->sizeHi); + } else if(!strcmp(name, "sizeLo")) { + return GINT_TO_POINTER(ctx->sizeLo); + } else if(!strcmp(name, "lenW")) { + return GINT_TO_POINTER(ctx->lenW); + } + + return NULL; +} + +static void +sha1_init(GaimCipherContext *context, void *extra) { + struct SHA1Context *sha1_ctx; + + sha1_ctx = g_new0(struct SHA1Context, 1); + + gaim_cipher_context_set_data(context, sha1_ctx); + + gaim_cipher_context_reset(context, extra); +} + +static void +sha1_reset(GaimCipherContext *context, void *extra) { + struct SHA1Context *sha1_ctx; + gint i; + + sha1_ctx = gaim_cipher_context_get_data(context); + + g_return_if_fail(sha1_ctx); + + sha1_ctx->lenW = 0; + sha1_ctx->sizeHi = 0; + sha1_ctx->sizeLo = 0; + + sha1_ctx->H[0] = 0x67452301; + sha1_ctx->H[1] = 0xEFCDAB89; + sha1_ctx->H[2] = 0x98BADCFE; + sha1_ctx->H[3] = 0x10325476; + sha1_ctx->H[4] = 0xC3D2E1F0; + + for(i = 0; i < 80; i++) + sha1_ctx->W[i] = 0; +} + +static void +sha1_uninit(GaimCipherContext *context) { + struct SHA1Context *sha1_ctx; + + gaim_cipher_context_reset(context, NULL); + + sha1_ctx = gaim_cipher_context_get_data(context); + + memset(sha1_ctx, 0, sizeof(struct SHA1Context)); + + g_free(sha1_ctx); + sha1_ctx = NULL; +} + + +static void +sha1_append(GaimCipherContext *context, const guchar *data, size_t len) { + struct SHA1Context *sha1_ctx; + gint i; + + sha1_ctx = gaim_cipher_context_get_data(context); + + g_return_if_fail(sha1_ctx); + + for(i = 0; i < len; i++) { + sha1_ctx->W[sha1_ctx->lenW / 4] <<= 8; + sha1_ctx->W[sha1_ctx->lenW / 4] |= data[i]; + + if((++sha1_ctx->lenW) % 64 == 0) { + sha1_hash_block(sha1_ctx); + sha1_ctx->lenW = 0; + } + + sha1_ctx->sizeLo += 8; + sha1_ctx->sizeHi += (sha1_ctx->sizeLo < 8); + } +} + +static gboolean +sha1_digest(GaimCipherContext *context, size_t in_len, guchar digest[20], + size_t *out_len) +{ + struct SHA1Context *sha1_ctx; + guchar pad0x80 = 0x80, pad0x00 = 0x00; + guchar padlen[8]; + gint i; + + g_return_val_if_fail(in_len >= 20, FALSE); + + sha1_ctx = gaim_cipher_context_get_data(context); + + g_return_val_if_fail(sha1_ctx, FALSE); + + padlen[0] = (guchar)((sha1_ctx->sizeHi >> 24) & 255); + padlen[1] = (guchar)((sha1_ctx->sizeHi >> 16) & 255); + padlen[2] = (guchar)((sha1_ctx->sizeHi >> 8) & 255); + padlen[3] = (guchar)((sha1_ctx->sizeHi >> 0) & 255); + padlen[4] = (guchar)((sha1_ctx->sizeLo >> 24) & 255); + padlen[5] = (guchar)((sha1_ctx->sizeLo >> 16) & 255); + padlen[6] = (guchar)((sha1_ctx->sizeLo >> 8) & 255); + padlen[7] = (guchar)((sha1_ctx->sizeLo >> 0) & 255); + + /* pad with a 1, then zeroes, then length */ + gaim_cipher_context_append(context, &pad0x80, 1); + while(sha1_ctx->lenW != 56) + gaim_cipher_context_append(context, &pad0x00, 1); + gaim_cipher_context_append(context, padlen, 8); + + for(i = 0; i < 20; i++) { + digest[i] = (guchar)(sha1_ctx->H[i / 4] >> 24); + sha1_ctx->H[i / 4] <<= 8; + } + + gaim_cipher_context_reset(context, NULL); + + if(out_len) + *out_len = 20; + + return TRUE; +} + +static GaimCipherOps SHA1Ops = { + sha1_set_opt, /* Set Option */ + sha1_get_opt, /* Get Option */ + sha1_init, /* init */ + sha1_reset, /* reset */ + sha1_uninit, /* uninit */ + NULL, /* set iv */ + sha1_append, /* append */ + sha1_digest, /* digest */ + NULL, /* encrypt */ + NULL, /* decrypt */ + NULL, /* set salt */ + NULL, /* get salt size */ + NULL, /* set key */ + NULL /* get key size */ +}; + +/******************************************************************************* + * Structs + ******************************************************************************/ +struct _GaimCipher { + gchar *name; + GaimCipherOps *ops; + guint ref; +}; + +struct _GaimCipherContext { + GaimCipher *cipher; + gpointer data; +}; + +/****************************************************************************** + * Globals + *****************************************************************************/ +static GList *ciphers = NULL; + +/****************************************************************************** + * GaimCipher API + *****************************************************************************/ +const gchar * +gaim_cipher_get_name(GaimCipher *cipher) { + g_return_val_if_fail(cipher, NULL); + + return cipher->name; +} + +guint +gaim_cipher_get_capabilities(GaimCipher *cipher) { + GaimCipherOps *ops = NULL; + guint caps = 0; + + g_return_val_if_fail(cipher, 0); + + ops = cipher->ops; + g_return_val_if_fail(ops, 0); + + if(ops->set_option) + caps |= GAIM_CIPHER_CAPS_SET_OPT; + if(ops->get_option) + caps |= GAIM_CIPHER_CAPS_GET_OPT; + if(ops->init) + caps |= GAIM_CIPHER_CAPS_INIT; + if(ops->reset) + caps |= GAIM_CIPHER_CAPS_RESET; + if(ops->uninit) + caps |= GAIM_CIPHER_CAPS_UNINIT; + if(ops->set_iv) + caps |= GAIM_CIPHER_CAPS_SET_IV; + if(ops->append) + caps |= GAIM_CIPHER_CAPS_APPEND; + if(ops->digest) + caps |= GAIM_CIPHER_CAPS_DIGEST; + if(ops->encrypt) + caps |= GAIM_CIPHER_CAPS_ENCRYPT; + if(ops->decrypt) + caps |= GAIM_CIPHER_CAPS_DECRYPT; + if(ops->set_salt) + caps |= GAIM_CIPHER_CAPS_SET_SALT; + if(ops->get_salt_size) + caps |= GAIM_CIPHER_CAPS_GET_SALT_SIZE; + if(ops->set_key) + caps |= GAIM_CIPHER_CAPS_SET_KEY; + if(ops->get_key_size) + caps |= GAIM_CIPHER_CAPS_GET_KEY_SIZE; + + return caps; +} + +gboolean +gaim_cipher_digest_region(const gchar *name, const guchar *data, + size_t data_len, size_t in_len, + guchar digest[], size_t *out_len) +{ + GaimCipher *cipher; + GaimCipherContext *context; + gboolean ret = FALSE; + + g_return_val_if_fail(name, FALSE); + g_return_val_if_fail(data, FALSE); + + cipher = gaim_ciphers_find_cipher(name); + + g_return_val_if_fail(cipher, FALSE); + + if(!cipher->ops->append || !cipher->ops->digest) { + gaim_debug_info("cipher", "gaim_cipher_region failed: " + "the %s cipher does not support appending and or " + "digesting.", cipher->name); + return FALSE; + } + + context = gaim_cipher_context_new(cipher, NULL); + gaim_cipher_context_append(context, data, data_len); + ret = gaim_cipher_context_digest(context, in_len, digest, out_len); + gaim_cipher_context_destroy(context); + + return ret; +} + +/****************************************************************************** + * GaimCiphers API + *****************************************************************************/ +GaimCipher * +gaim_ciphers_find_cipher(const gchar *name) { + GaimCipher *cipher; + GList *l; + + g_return_val_if_fail(name, NULL); + + for(l = ciphers; l; l = l->next) { + cipher = GAIM_CIPHER(l->data); + + if(!g_ascii_strcasecmp(cipher->name, name)) + return cipher; + } + + return NULL; +} + +GaimCipher * +gaim_ciphers_register_cipher(const gchar *name, GaimCipherOps *ops) { + GaimCipher *cipher = NULL; + + g_return_val_if_fail(name, NULL); + g_return_val_if_fail(ops, NULL); + g_return_val_if_fail(!gaim_ciphers_find_cipher(name), NULL); + + cipher = g_new0(GaimCipher, 1); + GAIM_DBUS_REGISTER_POINTER(cipher, GaimCipher); + + cipher->name = g_strdup(name); + cipher->ops = ops; + + ciphers = g_list_append(ciphers, cipher); + + gaim_signal_emit(gaim_ciphers_get_handle(), "cipher-added", cipher); + + return cipher; +} + +gboolean +gaim_ciphers_unregister_cipher(GaimCipher *cipher) { + g_return_val_if_fail(cipher, FALSE); + g_return_val_if_fail(cipher->ref == 0, FALSE); + + gaim_signal_emit(gaim_ciphers_get_handle(), "cipher-removed", cipher); + + ciphers = g_list_remove(ciphers, cipher); + + g_free(cipher->name); + + GAIM_DBUS_UNREGISTER_POINTER(cipher); + g_free(cipher); + + return TRUE; +} + +GList * +gaim_ciphers_get_ciphers() { + return ciphers; +} + +/****************************************************************************** + * GaimCipher Subsystem API + *****************************************************************************/ +gpointer +gaim_ciphers_get_handle() { + static gint handle; + + return &handle; +} + +void +gaim_ciphers_init() { + gpointer handle; + + handle = gaim_ciphers_get_handle(); + + gaim_signal_register(handle, "cipher-added", + gaim_marshal_VOID__POINTER, NULL, 1, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_CIPHER)); + gaim_signal_register(handle, "cipher-removed", + gaim_marshal_VOID__POINTER, NULL, 1, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_CIPHER)); + + gaim_ciphers_register_cipher("md5", &MD5Ops); + gaim_ciphers_register_cipher("sha1", &SHA1Ops); + gaim_ciphers_register_cipher("md4", &MD4Ops); + gaim_ciphers_register_cipher("des", &DESOps); +} + +void +gaim_ciphers_uninit() { + GaimCipher *cipher; + GList *l, *ll; + + for(l = ciphers; l; l = ll) { + ll = l->next; + + cipher = GAIM_CIPHER(l->data); + gaim_ciphers_unregister_cipher(cipher); + + ciphers = g_list_remove(ciphers, cipher); + } + + g_list_free(ciphers); + + gaim_signals_unregister_by_instance(gaim_ciphers_get_handle()); +} +/****************************************************************************** + * GaimCipherContext API + *****************************************************************************/ +void +gaim_cipher_context_set_option(GaimCipherContext *context, const gchar *name, + gpointer value) +{ + GaimCipher *cipher = NULL; + + g_return_if_fail(context); + g_return_if_fail(name); + + cipher = context->cipher; + g_return_if_fail(cipher); + + if(cipher->ops && cipher->ops->set_option) + cipher->ops->set_option(context, name, value); + else + gaim_debug_info("cipher", "the %s cipher does not support the " + "set_option operation\n", cipher->name); +} + +gpointer +gaim_cipher_context_get_option(GaimCipherContext *context, const gchar *name) { + GaimCipher *cipher = NULL; + + g_return_val_if_fail(context, NULL); + g_return_val_if_fail(name, NULL); + + cipher = context->cipher; + g_return_val_if_fail(cipher, NULL); + + if(cipher->ops && cipher->ops->get_option) + return cipher->ops->get_option(context, name); + else { + gaim_debug_info("cipher", "the %s cipher does not support the " + "get_option operation\n", cipher->name); + + return NULL; + } +} + +GaimCipherContext * +gaim_cipher_context_new(GaimCipher *cipher, void *extra) { + GaimCipherContext *context = NULL; + + g_return_val_if_fail(cipher, NULL); + + cipher->ref++; + + context = g_new0(GaimCipherContext, 1); + context->cipher = cipher; + + if(cipher->ops->init) + cipher->ops->init(context, extra); + + return context; +} + +GaimCipherContext * +gaim_cipher_context_new_by_name(const gchar *name, void *extra) { + GaimCipher *cipher; + + g_return_val_if_fail(name, NULL); + + cipher = gaim_ciphers_find_cipher(name); + + g_return_val_if_fail(cipher, NULL); + + return gaim_cipher_context_new(cipher, extra); +} + +void +gaim_cipher_context_reset(GaimCipherContext *context, void *extra) { + GaimCipher *cipher = NULL; + + g_return_if_fail(context); + + cipher = context->cipher; + g_return_if_fail(cipher); + + if(cipher->ops && cipher->ops->reset) + context->cipher->ops->reset(context, extra); +} + +void +gaim_cipher_context_destroy(GaimCipherContext *context) { + GaimCipher *cipher = NULL; + + g_return_if_fail(context); + + cipher = context->cipher; + g_return_if_fail(cipher); + + cipher->ref--; + + if(cipher->ops && cipher->ops->uninit) + cipher->ops->uninit(context); + + memset(context, 0, sizeof(context)); + g_free(context); + context = NULL; +} + +void +gaim_cipher_context_set_iv(GaimCipherContext *context, guchar *iv, size_t len) +{ + GaimCipher *cipher = NULL; + + g_return_if_fail(context); + g_return_if_fail(iv); + + cipher = context->cipher; + g_return_if_fail(cipher); + + if(cipher->ops && cipher->ops->set_iv) + cipher->ops->set_iv(context, iv, len); + else + gaim_debug_info("cipher", "the %s cipher does not support the set" + "initialization vector operation\n", cipher->name); +} + +void +gaim_cipher_context_append(GaimCipherContext *context, const guchar *data, + size_t len) +{ + GaimCipher *cipher = NULL; + + g_return_if_fail(context); + + cipher = context->cipher; + g_return_if_fail(cipher); + + if(cipher->ops && cipher->ops->append) + cipher->ops->append(context, data, len); + else + gaim_debug_info("cipher", "the %s cipher does not support the append " + "operation\n", cipher->name); +} + +gboolean +gaim_cipher_context_digest(GaimCipherContext *context, size_t in_len, + guchar digest[], size_t *out_len) +{ + GaimCipher *cipher = NULL; + + g_return_val_if_fail(context, FALSE); + + cipher = context->cipher; + g_return_val_if_fail(context, FALSE); + + if(cipher->ops && cipher->ops->digest) + return cipher->ops->digest(context, in_len, digest, out_len); + else { + gaim_debug_info("cipher", "the %s cipher does not support the digest " + "operation\n", cipher->name); + return FALSE; + } +} + +gboolean +gaim_cipher_context_digest_to_str(GaimCipherContext *context, size_t in_len, + gchar digest_s[], size_t *out_len) +{ + /* 8k is a bit excessive, will tweak later. */ + guchar digest[BUF_LEN * 4]; + gint n = 0; + size_t dlen = 0; + + g_return_val_if_fail(context, FALSE); + g_return_val_if_fail(digest_s, FALSE); + + if(!gaim_cipher_context_digest(context, sizeof(digest), digest, &dlen)) + return FALSE; + + /* in_len must be greater than dlen * 2 so we have room for the NUL. */ + if(in_len <= dlen * 2) + return FALSE; + + for(n = 0; n < dlen; n++) + sprintf(digest_s + (n * 2), "%02x", digest[n]); + + digest_s[n * 2] = '\0'; + + if(out_len) + *out_len = dlen * 2; + + return TRUE; +} + +gint +gaim_cipher_context_encrypt(GaimCipherContext *context, const guchar data[], + size_t len, guchar output[], size_t *outlen) +{ + GaimCipher *cipher = NULL; + + g_return_val_if_fail(context, -1); + + cipher = context->cipher; + g_return_val_if_fail(cipher, -1); + + if(cipher->ops && cipher->ops->encrypt) + return cipher->ops->encrypt(context, data, len, output, outlen); + else { + gaim_debug_info("cipher", "the %s cipher does not support the encrypt" + "operation\n", cipher->name); + + if(outlen) + *outlen = -1; + + return -1; + } +} + +gint +gaim_cipher_context_decrypt(GaimCipherContext *context, const guchar data[], + size_t len, guchar output[], size_t *outlen) +{ + GaimCipher *cipher = NULL; + + g_return_val_if_fail(context, -1); + + cipher = context->cipher; + g_return_val_if_fail(cipher, -1); + + if(cipher->ops && cipher->ops->decrypt) + return cipher->ops->decrypt(context, data, len, output, outlen); + else { + gaim_debug_info("cipher", "the %s cipher does not support the decrypt" + "operation\n", cipher->name); + + if(outlen) + *outlen = -1; + + return -1; + } +} + +void +gaim_cipher_context_set_salt(GaimCipherContext *context, guchar *salt) { + GaimCipher *cipher = NULL; + + g_return_if_fail(context); + + cipher = context->cipher; + g_return_if_fail(cipher); + + if(cipher->ops && cipher->ops->set_salt) + cipher->ops->set_salt(context, salt); + else + gaim_debug_info("cipher", "the %s cipher does not support the " + "set_salt operation\n", cipher->name); +} + +size_t +gaim_cipher_context_get_salt_size(GaimCipherContext *context) { + GaimCipher *cipher = NULL; + + g_return_val_if_fail(context, -1); + + cipher = context->cipher; + g_return_val_if_fail(cipher, -1); + + if(cipher->ops && cipher->ops->get_salt_size) + return cipher->ops->get_salt_size(context); + else { + gaim_debug_info("cipher", "the %s cipher does not support the " + "get_salt_size operation\n", cipher->name); + + return -1; + } +} + +void +gaim_cipher_context_set_key(GaimCipherContext *context, const guchar *key) { + GaimCipher *cipher = NULL; + + g_return_if_fail(context); + + cipher = context->cipher; + g_return_if_fail(cipher); + + if(cipher->ops && cipher->ops->set_key) + cipher->ops->set_key(context, key); + else + gaim_debug_info("cipher", "the %s cipher does not support the " + "set_key operation\n", cipher->name); +} + +size_t +gaim_cipher_context_get_key_size(GaimCipherContext *context) { + GaimCipher *cipher = NULL; + + g_return_val_if_fail(context, -1); + + cipher = context->cipher; + g_return_val_if_fail(cipher, -1); + + if(cipher->ops && cipher->ops->get_key_size) + return cipher->ops->get_key_size(context); + else { + gaim_debug_info("cipher", "the %s cipher does not support the " + "get_key_size operation\n", cipher->name); + + return -1; + } +} + +void +gaim_cipher_context_set_data(GaimCipherContext *context, gpointer data) { + g_return_if_fail(context); + + context->data = data; +} + +gpointer +gaim_cipher_context_get_data(GaimCipherContext *context) { + g_return_val_if_fail(context, NULL); + + return context->data; +} + +gchar *gaim_cipher_http_digest_calculate_session_key( + const gchar *algorithm, + const gchar *username, + const gchar *realm, + const gchar *password, + const gchar *nonce, + const gchar *client_nonce) +{ + GaimCipher *cipher; + GaimCipherContext *context; + gchar hash[33]; /* We only support MD5. */ + + g_return_val_if_fail(username != NULL, NULL); + g_return_val_if_fail(realm != NULL, NULL); + g_return_val_if_fail(password != NULL, NULL); + g_return_val_if_fail(nonce != NULL, NULL); + + /* Check for a supported algorithm. */ + g_return_val_if_fail(algorithm == NULL || + *algorithm == '\0' || + strcasecmp(algorithm, "MD5") || + strcasecmp(algorithm, "MD5-sess"), NULL); + + cipher = gaim_ciphers_find_cipher("md5"); + g_return_val_if_fail(cipher != NULL, NULL); + + context = gaim_cipher_context_new(cipher, NULL); + + gaim_cipher_context_append(context, (guchar *)username, strlen(username)); + gaim_cipher_context_append(context, (guchar *)":", 1); + gaim_cipher_context_append(context, (guchar *)realm, strlen(realm)); + gaim_cipher_context_append(context, (guchar *)":", 1); + gaim_cipher_context_append(context, (guchar *)password, strlen(password)); + + if (algorithm != NULL && !strcasecmp(algorithm, "MD5-sess")) + { + guchar digest[16]; + + if (client_nonce == NULL) + { + gaim_cipher_context_destroy(context); + gaim_debug_error("cipher", "Required client_nonce missing for MD5-sess digest calculation."); + return NULL; + } + + gaim_cipher_context_digest(context, sizeof(digest), digest, NULL); + gaim_cipher_context_destroy(context); + + context = gaim_cipher_context_new(cipher, NULL); + gaim_cipher_context_append(context, digest, sizeof(digest)); + gaim_cipher_context_append(context, (guchar *)":", 1); + gaim_cipher_context_append(context, (guchar *)nonce, strlen(nonce)); + gaim_cipher_context_append(context, (guchar *)":", 1); + gaim_cipher_context_append(context, (guchar *)client_nonce, strlen(client_nonce)); + } + + gaim_cipher_context_digest_to_str(context, sizeof(hash), hash, NULL); + gaim_cipher_context_destroy(context); + + return g_strdup(hash); +} + +gchar *gaim_cipher_http_digest_calculate_response( + const gchar *algorithm, + const gchar *method, + const gchar *digest_uri, + const gchar *qop, + const gchar *entity, + const gchar *nonce, + const gchar *nonce_count, + const gchar *client_nonce, + const gchar *session_key) +{ + GaimCipher *cipher; + GaimCipherContext *context; + static gchar hash2[33]; /* We only support MD5. */ + + g_return_val_if_fail(method != NULL, NULL); + g_return_val_if_fail(digest_uri != NULL, NULL); + g_return_val_if_fail(nonce != NULL, NULL); + g_return_val_if_fail(session_key != NULL, NULL); + + /* Check for a supported algorithm. */ + g_return_val_if_fail(algorithm == NULL || + *algorithm == '\0' || + strcasecmp(algorithm, "MD5") || + strcasecmp(algorithm, "MD5-sess"), NULL); + + /* Check for a supported "quality of protection". */ + g_return_val_if_fail(qop == NULL || + *qop == '\0' || + strcasecmp(qop, "auth") || + strcasecmp(qop, "auth-int"), NULL); + + cipher = gaim_ciphers_find_cipher("md5"); + g_return_val_if_fail(cipher != NULL, NULL); + + context = gaim_cipher_context_new(cipher, NULL); + + gaim_cipher_context_append(context, (guchar *)method, strlen(method)); + gaim_cipher_context_append(context, (guchar *)":", 1); + gaim_cipher_context_append(context, (guchar *)digest_uri, strlen(digest_uri)); + + if (qop != NULL && !strcasecmp(qop, "auth-int")) + { + GaimCipherContext *context2; + gchar entity_hash[33]; + + if (entity == NULL) + { + gaim_cipher_context_destroy(context); + gaim_debug_error("cipher", "Required entity missing for auth-int digest calculation."); + return NULL; + } + + context2 = gaim_cipher_context_new(cipher, NULL); + gaim_cipher_context_append(context2, (guchar *)entity, strlen(entity)); + gaim_cipher_context_digest_to_str(context2, sizeof(entity_hash), entity_hash, NULL); + gaim_cipher_context_destroy(context2); + + gaim_cipher_context_append(context, (guchar *)":", 1); + gaim_cipher_context_append(context, (guchar *)entity_hash, strlen(entity_hash)); + } + + gaim_cipher_context_digest_to_str(context, sizeof(hash2), hash2, NULL); + gaim_cipher_context_destroy(context); + + context = gaim_cipher_context_new(cipher, NULL); + gaim_cipher_context_append(context, (guchar *)session_key, strlen(session_key)); + gaim_cipher_context_append(context, (guchar *)":", 1); + gaim_cipher_context_append(context, (guchar *)nonce, strlen(nonce)); + gaim_cipher_context_append(context, (guchar *)":", 1); + + if (qop != NULL && *qop != '\0') + { + if (nonce_count == NULL) + { + gaim_cipher_context_destroy(context); + gaim_debug_error("cipher", "Required nonce_count missing for digest calculation."); + return NULL; + } + + if (client_nonce == NULL) + { + gaim_cipher_context_destroy(context); + gaim_debug_error("cipher", "Required client_nonce missing for digest calculation."); + return NULL; + } + + gaim_cipher_context_append(context, (guchar *)nonce_count, strlen(nonce_count)); + gaim_cipher_context_append(context, (guchar *)":", 1); + gaim_cipher_context_append(context, (guchar *)client_nonce, strlen(client_nonce)); + gaim_cipher_context_append(context, (guchar *)":", 1); + + gaim_cipher_context_append(context, (guchar *)qop, strlen(qop)); + + gaim_cipher_context_append(context, (guchar *)":", 1); + } + + gaim_cipher_context_append(context, (guchar *)hash2, strlen(hash2)); + gaim_cipher_context_digest_to_str(context, sizeof(hash2), hash2, NULL); + gaim_cipher_context_destroy(context); + + return g_strdup(hash2); +} diff -r d10dda2777a9 -r b63ebf84c42b core/cipher.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/cipher.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,443 @@ +/** + * @file cipher.h Gaim Cipher API + * @ingroup core + * + * 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 + */ +#ifndef GAIM_CIPHER_H +#define GAIM_CIPHER_H + +#include + +#define GAIM_CIPHER(obj) ((GaimCipher *)(obj)) /**< GaimCipher typecast helper */ +#define GAIM_CIPHER_OPS(obj) ((GaimCipherOps *)(obj)) /**< GaimCipherInfo typecase helper */ +#define GAIM_CIPHER_CONTEXT(obj) ((GaimCipherContext *)(obj)) /**< GaimCipherContext typecast helper */ + +typedef struct _GaimCipher GaimCipher; /**< A handle to a GaimCipher */ +typedef struct _GaimCipherOps GaimCipherOps; /**< Ops for a GaimCipher */ +typedef struct _GaimCipherContext GaimCipherContext; /**< A context for a GaimCipher */ + + +/** + * The operation flags for a cipher + */ +typedef enum _GaimCipherCaps { + GAIM_CIPHER_CAPS_SET_OPT = 1 << 1, /**< Set option flag */ + GAIM_CIPHER_CAPS_GET_OPT = 1 << 2, /**< Get option flag */ + GAIM_CIPHER_CAPS_INIT = 1 << 3, /**< Init flag */ + GAIM_CIPHER_CAPS_RESET = 1 << 4, /**< Reset flag */ + GAIM_CIPHER_CAPS_UNINIT = 1 << 5, /**< Uninit flag */ + GAIM_CIPHER_CAPS_SET_IV = 1 << 6, /**< Set IV flag */ + GAIM_CIPHER_CAPS_APPEND = 1 << 7, /**< Append flag */ + GAIM_CIPHER_CAPS_DIGEST = 1 << 8, /**< Digest flag */ + GAIM_CIPHER_CAPS_ENCRYPT = 1 << 9, /**< Encrypt flag */ + GAIM_CIPHER_CAPS_DECRYPT = 1 << 10, /**< Decrypt flag */ + GAIM_CIPHER_CAPS_SET_SALT = 1 << 11, /**< Set salt flag */ + GAIM_CIPHER_CAPS_GET_SALT_SIZE = 1 << 12, /**< Get salt size flag */ + GAIM_CIPHER_CAPS_SET_KEY = 1 << 13, /**< Set key flag */ + GAIM_CIPHER_CAPS_GET_KEY_SIZE = 1 << 14, /**< Get key size flag */ + GAIM_CIPHER_CAPS_UNKNOWN = 1 << 16 /**< Unknown */ +} GaimCipherCaps; + +/** + * The operations of a cipher. Every cipher must implement one of these. + */ +struct _GaimCipherOps { + /** The set option function */ + void (*set_option)(GaimCipherContext *context, const gchar *name, void *value); + + /** The get option function */ + void *(*get_option)(GaimCipherContext *context, const gchar *name); + + /** The init function */ + void (*init)(GaimCipherContext *context, void *extra); + + /** The reset function */ + void (*reset)(GaimCipherContext *context, void *extra); + + /** The uninit function */ + void (*uninit)(GaimCipherContext *context); + + /** The set initialization vector function */ + void (*set_iv)(GaimCipherContext *context, guchar *iv, size_t len); + + /** The append data function */ + void (*append)(GaimCipherContext *context, const guchar *data, size_t len); + + /** The digest function */ + gboolean (*digest)(GaimCipherContext *context, size_t in_len, guchar digest[], size_t *out_len); + + /** The encrypt function */ + int (*encrypt)(GaimCipherContext *context, const guchar data[], size_t len, guchar output[], size_t *outlen); + + /** The decrypt function */ + int (*decrypt)(GaimCipherContext *context, const guchar data[], size_t len, guchar output[], size_t *outlen); + + /** The set salt function */ + void (*set_salt)(GaimCipherContext *context, guchar *salt); + + /** The get salt size function */ + size_t (*get_salt_size)(GaimCipherContext *context); + + /** The set key function */ + void (*set_key)(GaimCipherContext *context, const guchar *key); + + /** The get key size function */ + size_t (*get_key_size)(GaimCipherContext *context); +}; + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/*****************************************************************************/ +/** @name GaimCipher API */ +/*****************************************************************************/ +/*@{*/ + +/** + * Gets a cipher's name + * + * @param cipher The cipher handle + * + * @return The cipher's name + */ +const gchar *gaim_cipher_get_name(GaimCipher *cipher); + +/** + * Gets a cipher's capabilities + * + * @param cipher The cipher handle + * + * @return The cipher's info + */ +guint gaim_cipher_get_capabilities(GaimCipher *cipher); + +/** + * Gets a digest from a cipher + * + * @param name The cipher's name + * @param data The data to hash + * @param data_len The length of the data + * @param in_len The length of the buffer + * @param digest The returned digest + * @param out_len The length written + * + * @return @c TRUE if successful, @c FALSE otherwise + */ +gboolean gaim_cipher_digest_region(const gchar *name, const guchar *data, size_t data_len, size_t in_len, guchar digest[], size_t *out_len); + +/*@}*/ +/******************************************************************************/ +/** @name GaimCiphers API */ +/******************************************************************************/ +/*@{*/ + +/** + * Finds a cipher by it's name + * + * @param name The name of the cipher to find + * + * @return The cipher handle or @c NULL + */ +GaimCipher *gaim_ciphers_find_cipher(const gchar *name); + +/** + * Registers a cipher as a usable cipher + * + * @param name The name of the new cipher + * @param ops The cipher ops to register + * + * @return The handle to the new cipher or @c NULL if it failed + */ +GaimCipher *gaim_ciphers_register_cipher(const gchar *name, GaimCipherOps *ops); + +/** + * Unregisters a cipher + * + * @param cipher The cipher handle to unregister + * + * @return Whether or not the cipher was successfully unloaded + */ +gboolean gaim_ciphers_unregister_cipher(GaimCipher *cipher); + +/** + * Gets the list of ciphers + * + * @return The list of available ciphers + * @note This list should not be modified, it is owned by the cipher core + */ +GList *gaim_ciphers_get_ciphers(void); + +/*@}*/ +/******************************************************************************/ +/** @name GaimCipher Subsystem API */ +/******************************************************************************/ +/*@{*/ + +/** + * Gets the handle to the cipher subsystem + * + * @return The handle to the cipher subsystem + */ +gpointer gaim_ciphers_get_handle(void); + +/** + * Initializes the cipher core + */ +void gaim_ciphers_init(void); + +/** + * Uninitializes the cipher core + */ +void gaim_ciphers_uninit(void); + +/*@}*/ +/******************************************************************************/ +/** @name GaimCipherContext API */ +/******************************************************************************/ +/*@{*/ + +/** + * Sets the value an option on a cipher context + * + * @param context The cipher context + * @param name The name of the option + * @param value The value to set + */ +void gaim_cipher_context_set_option(GaimCipherContext *context, const gchar *name, gpointer value); + +/** + * Gets the vale of an option on a cipher context + * + * @param context The cipher context + * @param name The name of the option + * @return The value of the option + */ +gpointer gaim_cipher_context_get_option(GaimCipherContext *context, const gchar *name); + +/** + * Creates a new cipher context and initializes it + * + * @param cipher The cipher to use + * @param extra Extra data for the specific cipher + * + * @return The new cipher context + */ +GaimCipherContext *gaim_cipher_context_new(GaimCipher *cipher, void *extra); + +/** + * Creates a new cipher context by the cipher name and initializes it + * + * @param name The cipher's name + * @param extra Extra data for the specific cipher + * + * @return The new cipher context + */ +GaimCipherContext *gaim_cipher_context_new_by_name(const gchar *name, void *extra); + +/** + * Resets a cipher context to it's default value + * @note If you have set an IV you will have to set it after resetting + * + * @param context The context to reset + * @param extra Extra data for the specific cipher + */ +void gaim_cipher_context_reset(GaimCipherContext *context, gpointer extra); + +/** + * Destorys a cipher context and deinitializes it + * + * @param context The cipher context to destory + */ +void gaim_cipher_context_destroy(GaimCipherContext *context); + +/** + * Sets the initialization vector for a context + * @note This should only be called right after a cipher context is created or reset + * + * @param context The context to set the IV to + * @param iv The initialization vector to set + * @param len The len of the IV + */ +void gaim_cipher_context_set_iv(GaimCipherContext *context, guchar *iv, size_t len); + +/** + * Appends data to the context + * + * @param context The context to append data to + * @param data The data to append + * @param len The length of the data + */ +void gaim_cipher_context_append(GaimCipherContext *context, const guchar *data, size_t len); + +/** + * Digests a context + * + * @param context The context to digest + * @param in_len The length of the buffer + * @param digest The return buffer for the digest + * @param out_len The length of the returned value + */ +gboolean gaim_cipher_context_digest(GaimCipherContext *context, size_t in_len, guchar digest[], size_t *out_len); + +/** + * Converts a guchar digest into a hex string + * + * @param context The context to get a digest from + * @param in_len The length of the buffer + * @param digest_s The return buffer for the string digest + * @param out_len The length of the returned value + */ +gboolean gaim_cipher_context_digest_to_str(GaimCipherContext *context, size_t in_len, gchar digest_s[], size_t *out_len); + +/** + * Encrypts data using the context + * + * @param context The context + * @param data The data to encrypt + * @param len The length of the data + * @param output The output buffer + * @param outlen The len of data that was outputed + * + * @return A cipher specific status code + */ +gint gaim_cipher_context_encrypt(GaimCipherContext *context, const guchar data[], size_t len, guchar output[], size_t *outlen); + +/** + * Decrypts data using the context + * + * @param context The context + * @param data The data to encrypt + * @param len The length of the returned value + * @param output The output buffer + * @param outlen The len of data that was outputed + * + * @return A cipher specific status code + */ +gint gaim_cipher_context_decrypt(GaimCipherContext *context, const guchar data[], size_t len, guchar output[], size_t *outlen); + +/** + * Sets the salt on a context + * + * @param context The context who's salt to set + * @param salt The salt + */ +void gaim_cipher_context_set_salt(GaimCipherContext *context, guchar *salt); + +/** + * Gets the size of the salt if the cipher supports it + * + * @param context The context who's salt size to get + * + * @return The size of the salt + */ +size_t gaim_cipher_context_get_salt_size(GaimCipherContext *context); + +/** + * Sets the key on a context + * + * @param context The context who's key to set + * @param key The key + */ +void gaim_cipher_context_set_key(GaimCipherContext *context, const guchar *key); + +/** + * Gets the key size for a context + * + * @param context The context who's key size to get + * + * @return The size of the key + */ +size_t gaim_cipher_context_get_key_size(GaimCipherContext *context); + +/** + * Sets the cipher data for a context + * + * @param context The context who's cipher data to set + * @param data The cipher data to set + */ +void gaim_cipher_context_set_data(GaimCipherContext *context, gpointer data); + +/** + * Gets the cipher data for a context + * + * @param context The context who's cipher data to get + * + * @return The cipher data + */ +gpointer gaim_cipher_context_get_data(GaimCipherContext *context); + +/*@}*/ +/*****************************************************************************/ +/** @name Gaim Cipher HTTP Digest Helper Functions */ +/*****************************************************************************/ +/*@{*/ + +/** + * Calculates a session key for HTTP Digest authentation + * + * See RFC 2617 for more information. + * + * @param algorithm The hash algorithm to use + * @param username The username provided by the user + * @param realm The authentication realm provided by the server + * @param password The password provided by the user + * @param nonce The nonce provided by the server + * @param client_nonce The nonce provided by the client + * + * @return The session key, or @c NULL if an error occurred. + */ +gchar *gaim_cipher_http_digest_calculate_session_key( + const gchar *algorithm, const gchar *username, + const gchar *realm, const gchar *password, + const gchar *nonce, const gchar *client_nonce); + +/** Calculate a response for HTTP Digest authentication + * + * See RFC 2617 for more information. + * + * @param algorithm The hash algorithm to use + * @param method The HTTP method in use + * @param digest_uri The URI from the initial request + * @param qop The "quality of protection" + * @param entity The entity body + * @param nonce The nonce provided by the server + * @param nonce_count The nonce count + * @param client_nonce The nonce provided by the client + * @param session_key The session key from gaim_cipher_http_digest_calculate_session_key() + * + * @return The hashed response, or @c NULL if an error occurred. + */ +gchar *gaim_cipher_http_digest_calculate_response( + const gchar *algorithm, const gchar *method, + const gchar *digest_uri, const gchar *qop, + const gchar *entity, const gchar *nonce, + const gchar *nonce_count, const gchar *client_nonce, + const gchar *session_key); + +/*@}*/ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* GAIM_CIPHER_H */ diff -r d10dda2777a9 -r b63ebf84c42b core/circbuffer.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/circbuffer.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,142 @@ +/* + * @file circbuffer.h Buffer Utility Functions + * @ingroup core + * + * 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 "circbuffer.h" + +#define DEFAULT_BUF_SIZE 256 + +GaimCircBuffer * +gaim_circ_buffer_new(gsize growsize) { + GaimCircBuffer *buf = g_new0(GaimCircBuffer, 1); + buf->growsize = growsize ? growsize : DEFAULT_BUF_SIZE; + return buf; +} + +void gaim_circ_buffer_destroy(GaimCircBuffer *buf) { + g_return_if_fail(buf); + g_free(buf->buffer); + g_free(buf); +} + +static void grow_circ_buffer(GaimCircBuffer *buf, gsize len) { + int in_offset = 0, out_offset = 0; + int start_buflen = buf->buflen; + + while ((buf->buflen - buf->bufused) < len) + buf->buflen += buf->growsize; + + if (buf->inptr != NULL) { + in_offset = buf->inptr - buf->buffer; + out_offset = buf->outptr - buf->buffer; + } + buf->buffer = g_realloc(buf->buffer, buf->buflen); + + /* adjust the fill and remove pointer locations */ + if (buf->inptr == NULL) { + buf->inptr = buf->outptr = buf->buffer; + } else { + buf->inptr = buf->buffer + in_offset; + buf->outptr = buf->buffer + out_offset; + } + + /* If the fill pointer is wrapped to before the remove + * pointer, we need to shift the data */ + if (in_offset < out_offset) { + int shift_n = MIN(buf->buflen - start_buflen, + in_offset); + memcpy(buf->buffer + start_buflen, buf->buffer, + shift_n); + + /* If we couldn't fit the wrapped read buffer + * at the end */ + if (shift_n < in_offset) { + memmove(buf->buffer, + buf->buffer + shift_n, + in_offset - shift_n); + buf->inptr = buf->buffer + + (in_offset - shift_n); + } else { + buf->inptr = buf->buffer + + start_buflen + in_offset; + } + } +} + +void gaim_circ_buffer_append(GaimCircBuffer *buf, gconstpointer src, gsize len) { + + int len_stored; + + /* Grow the buffer, if necessary */ + if ((buf->buflen - buf->bufused) < len) + grow_circ_buffer(buf, len); + + /* If there is not enough room to copy all of src before hitting + * the end of the buffer then we will need to do two copies. + * One copy from inptr to the end of the buffer, and the + * second copy from the start of the buffer to the end of src. */ + if (buf->inptr >= buf->outptr) + len_stored = MIN(len, buf->buflen + - (buf->inptr - buf->buffer)); + else + len_stored = len; + + memcpy(buf->inptr, src, len_stored); + + if (len_stored < len) { + memcpy(buf->buffer, src + len_stored, len - len_stored); + buf->inptr = buf->buffer + (len - len_stored); + } else if ((buf->buffer - buf->inptr) == len_stored) { + buf->inptr = buf->buffer; + } else { + buf->inptr += len_stored; + } + + buf->bufused += len; +} + +gsize gaim_circ_buffer_get_max_read(GaimCircBuffer *buf) { + int max_read; + + if (buf->bufused == 0) + max_read = 0; + else if ((buf->outptr - buf->inptr) >= 0) + max_read = buf->buflen - (buf->outptr - buf->buffer); + else + max_read = buf->inptr - buf->outptr; + + return max_read; +} + +gboolean gaim_circ_buffer_mark_read(GaimCircBuffer *buf, gsize len) { + g_return_val_if_fail(gaim_circ_buffer_get_max_read(buf) >= len, FALSE); + + buf->outptr += len; + buf->bufused -= len; + /* wrap to the start if we're at the end */ + if ((buf->outptr - buf->buffer) == buf->buflen) + buf->outptr = buf->buffer; + + return TRUE; +} + diff -r d10dda2777a9 -r b63ebf84c42b core/circbuffer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/circbuffer.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,117 @@ +/* + * @file circbuffer.h Buffer Utility Functions + * @ingroup core + * + * 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 + */ +#ifndef _CIRCBUFFER_H +#define _CIRCBUFFER_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct _GaimCircBuffer { + + /** A pointer to the starting address of our chunk of memory. */ + gchar *buffer; + + /** The incremental amount to increase this buffer by when + * the buffer is not big enough to hold incoming data, in bytes. */ + gsize growsize; + + /** The length of this buffer, in bytes. */ + gsize buflen; + + /** The number of bytes of this buffer that contain unread data. */ + gsize bufused; + + /** A pointer to the next byte where new incoming data is + * buffered to. */ + gchar *inptr; + + /** A pointer to the next byte of buffered data that should be + * read by the consumer. */ + gchar *outptr; + +} GaimCircBuffer; + +/** + * Creates a new circular buffer. This will not allocate any memory for the + * actual buffer until data is appended to it. + * + * @param growsize The amount that the buffer should grow the first time data + * is appended and every time more space is needed. Pass in + * "0" to use the default of 256 bytes. + * + * @return The new GaimCircBuffer. This should be freed with + * gaim_circ_buffer_destroy when you are done with it + */ +GaimCircBuffer *gaim_circ_buffer_new(gsize growsize); + +/** + * Dispose of the GaimCircBuffer and free any memory used by it (including any + * memory used by the internal buffer). + * + * @param buf The GaimCircBuffer to free + */ +void gaim_circ_buffer_destroy(GaimCircBuffer *buf); + +/** + * Append data to the GaimCircBuffer. This will grow the internal + * buffer to fit the added data, if needed. + * + * @param buf The GaimCircBuffer to which to append the data + * @param src pointer to the data to copy into the buffer + * @param len number of bytes to copy into the buffer + */ +void gaim_circ_buffer_append(GaimCircBuffer *buf, gconstpointer src, gsize len); + +/** + * Determine the maximum number of contiguous bytes that can be read from the + * GaimCircBuffer. + * Note: This may not be the total number of bytes that are buffered - a + * subsequent call after calling gaim_circ_buffer_mark_read() may indicate more + * data is available to read. + * + * @param buf the GaimCircBuffer for which to determine the maximum contiguous + * bytes that can be read. + * + * @return the number of bytes that can be read from the GaimCircBuffer + */ +gsize gaim_circ_buffer_get_max_read(GaimCircBuffer *buf); + +/** + * Mark the number of bytes that have been read from the buffer. + * + * @param buf The GaimCircBuffer to mark bytes read from + * @param len The number of bytes to mark as read + * + * @return TRUE if we successfully marked the bytes as having been read, FALSE + * otherwise. + */ +gboolean gaim_circ_buffer_mark_read(GaimCircBuffer *buf, gsize len); + +#ifdef __cplusplus +} +#endif + +#endif /* _CIRCBUFFER_H */ diff -r d10dda2777a9 -r b63ebf84c42b core/cmds.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/cmds.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,360 @@ +/** + * @file cmds.c Commands API + * @ingroup core + * + * Copyright (C) 2003-2004 Timothy Ringenbach + +#include "account.h" +#include "util.h" +#include "cmds.h" + +static GList *cmds = NULL; +static guint next_id = 1; + +typedef struct _GaimCmd { + GaimCmdId id; + gchar *cmd; + gchar *args; + GaimCmdPriority priority; + GaimCmdFlag flags; + gchar *prpl_id; + GaimCmdFunc func; + gchar *help; + void *data; +} GaimCmd; + + +static gint cmds_compare_func(const GaimCmd *a, const GaimCmd *b) +{ + if (a->priority > b->priority) + return -1; + else if (a->priority < b->priority) + return 1; + else return 0; +} + +GaimCmdId gaim_cmd_register(const gchar *cmd, const gchar *args, + GaimCmdPriority p, GaimCmdFlag f, + const gchar *prpl_id, GaimCmdFunc func, + const gchar *helpstr, void *data) +{ + GaimCmdId id; + GaimCmd *c; + + g_return_val_if_fail(cmd != NULL && *cmd != '\0', 0); + g_return_val_if_fail(args != NULL, 0); + g_return_val_if_fail(func != NULL, 0); + + id = next_id++; + + c = g_new0(GaimCmd, 1); + c->id = id; + c->cmd = g_strdup(cmd); + c->args = g_strdup(args); + c->priority = p; + c->flags = f; + c->prpl_id = g_strdup(prpl_id); + c->func = func; + c->help = g_strdup(helpstr); + c->data = data; + + cmds = g_list_insert_sorted(cmds, c, (GCompareFunc)cmds_compare_func); + + return id; +} + +static void gaim_cmd_free(GaimCmd *c) +{ + g_free(c->cmd); + g_free(c->args); + g_free(c->prpl_id); + g_free(c->help); + g_free(c); +} + +void gaim_cmd_unregister(GaimCmdId id) +{ + GaimCmd *c; + GList *l; + + for (l = cmds; l; l = l->next) { + c = l->data; + + if (c->id == id) { + cmds = g_list_remove(cmds, c); + gaim_cmd_free(c); + return; + } + } +} + +/** + * This sets args to a NULL-terminated array of strings. It should + * be freed using g_strfreev(). + */ +static gboolean gaim_cmd_parse_args(GaimCmd *cmd, const gchar *s, const gchar *m, gchar ***args) +{ + int i; + const char *end, *cur; + + *args = g_new0(char *, strlen(cmd->args) + 1); + + cur = s; + + for (i = 0; cmd->args[i]; i++) { + if (!*cur) + return (cmd->flags & GAIM_CMD_FLAG_ALLOW_WRONG_ARGS); + + switch (cmd->args[i]) { + case 'w': + if (!(end = strchr(cur, ' '))) { + end = cur + strlen(cur); + (*args)[i] = g_strndup(cur, end - cur); + cur = end; + } else { + (*args)[i] = g_strndup(cur, end - cur); + cur = end + 1; + } + break; + case 'W': + if (!(end = strchr(cur, ' '))) { + end = cur + strlen(cur); + (*args)[i] = gaim_markup_slice(m, g_utf8_pointer_to_offset(s, cur), g_utf8_pointer_to_offset(s, end)); + cur = end; + } else { + (*args)[i] = gaim_markup_slice(m, g_utf8_pointer_to_offset(s, cur), g_utf8_pointer_to_offset(s, end)); + cur = end +1; + } + break; + case 's': + (*args)[i] = g_strdup(cur); + cur = cur + strlen(cur); + break; + case 'S': + (*args)[i] = gaim_markup_slice(m, g_utf8_pointer_to_offset(s, cur), g_utf8_strlen(cur, -1) + 1); + cur = cur + strlen(cur); + break; + } + } + + if (*cur) + return (cmd->flags & GAIM_CMD_FLAG_ALLOW_WRONG_ARGS); + + return TRUE; +} + +static void gaim_cmd_strip_current_char(gunichar c, char *s, guint len) +{ + int bytes; + + bytes = g_unichar_to_utf8(c, NULL); + memmove(s, s + bytes, len + 1 - bytes); +} + +static void gaim_cmd_strip_cmd_from_markup(char *markup) +{ + guint len = strlen(markup); + char *s = markup; + + while (*s) { + gunichar c = g_utf8_get_char(s); + + if (c == '<') { + s = strchr(s, '>'); + if (!s) + return; + } else if (g_unichar_isspace(c)) { + gaim_cmd_strip_current_char(c, s, len - (s - markup)); + return; + } else { + gaim_cmd_strip_current_char(c, s, len - (s - markup)); + continue; + } + s = g_utf8_next_char(s); + } +} + +GaimCmdStatus gaim_cmd_do_command(GaimConversation *conv, const gchar *cmdline, + const gchar *markup, gchar **error) +{ + GaimCmd *c; + GList *l; + gchar *err = NULL; + gboolean is_im; + gboolean found = FALSE, tried_cmd = FALSE, right_type = FALSE, right_prpl = FALSE; + const gchar *prpl_id; + gchar **args = NULL; + gchar *cmd, *rest, *mrest; + GaimCmdRet ret = GAIM_CMD_RET_CONTINUE; + + *error = NULL; + prpl_id = gaim_account_get_protocol_id(gaim_conversation_get_account(conv)); + + if (gaim_conversation_get_type(conv) == GAIM_CONV_TYPE_IM) + is_im = TRUE; + else if (gaim_conversation_get_type(conv) == GAIM_CONV_TYPE_CHAT) + is_im = FALSE; + else + return GAIM_CMD_STATUS_FAILED; + + rest = strchr(cmdline, ' '); + if (rest) { + cmd = g_strndup(cmdline, rest - cmdline); + rest++; + } else { + cmd = g_strdup(cmdline); + rest = ""; + } + + mrest = g_strdup(markup); + gaim_cmd_strip_cmd_from_markup(mrest); + + for (l = cmds; l; l = l->next) { + c = l->data; + + if (strcmp(c->cmd, cmd) != 0) + continue; + + found = TRUE; + + if (is_im) + if (!(c->flags & GAIM_CMD_FLAG_IM)) + continue; + if (!is_im) + if (!(c->flags & GAIM_CMD_FLAG_CHAT)) + continue; + + right_type = TRUE; + + if ((c->flags & GAIM_CMD_FLAG_PRPL_ONLY) && c->prpl_id && + (strcmp(c->prpl_id, prpl_id) != 0)) + continue; + + right_prpl = TRUE; + + /* this checks the allow bad args flag for us */ + if (!gaim_cmd_parse_args(c, rest, mrest, &args)) { + g_strfreev(args); + args = NULL; + continue; + } + + tried_cmd = TRUE; + ret = c->func(conv, cmd, args, &err, c->data); + if (ret == GAIM_CMD_RET_CONTINUE) { + g_free(err); + err = NULL; + g_strfreev(args); + args = NULL; + continue; + } else { + break; + } + + } + + g_strfreev(args); + g_free(cmd); + g_free(mrest); + + if (!found) + return GAIM_CMD_STATUS_NOT_FOUND; + + if (!right_type) + return GAIM_CMD_STATUS_WRONG_TYPE; + if (!right_prpl) + return GAIM_CMD_STATUS_WRONG_PRPL; + if (!tried_cmd) + return GAIM_CMD_STATUS_WRONG_ARGS; + + if (ret == GAIM_CMD_RET_OK) { + return GAIM_CMD_STATUS_OK; + } else { + *error = err; + if (ret == GAIM_CMD_RET_CONTINUE) + return GAIM_CMD_STATUS_NOT_FOUND; + else + return GAIM_CMD_STATUS_FAILED; + } + +} + + +GList *gaim_cmd_list(GaimConversation *conv) +{ + GList *ret = NULL; + GaimCmd *c; + GList *l; + + for (l = cmds; l; l = l->next) { + c = l->data; + + if (conv && (gaim_conversation_get_type(conv) == GAIM_CONV_TYPE_IM)) + if (!(c->flags & GAIM_CMD_FLAG_IM)) + continue; + if (conv && (gaim_conversation_get_type(conv) == GAIM_CONV_TYPE_CHAT)) + if (!(c->flags & GAIM_CMD_FLAG_CHAT)) + continue; + + if (conv && (c->flags & GAIM_CMD_FLAG_PRPL_ONLY) && c->prpl_id && + (strcmp(c->prpl_id, gaim_account_get_protocol_id(gaim_conversation_get_account(conv))) != 0)) + continue; + + ret = g_list_append(ret, c->cmd); + } + + ret = g_list_sort(ret, (GCompareFunc)strcmp); + + return ret; +} + + +GList *gaim_cmd_help(GaimConversation *conv, const gchar *cmd) +{ + GList *ret = NULL; + GaimCmd *c; + GList *l; + + for (l = cmds; l; l = l->next) { + c = l->data; + + if (cmd && (strcmp(cmd, c->cmd) != 0)) + continue; + + if (conv && (gaim_conversation_get_type(conv) == GAIM_CONV_TYPE_IM)) + if (!(c->flags & GAIM_CMD_FLAG_IM)) + continue; + if (conv && (gaim_conversation_get_type(conv) == GAIM_CONV_TYPE_CHAT)) + if (!(c->flags & GAIM_CMD_FLAG_CHAT)) + continue; + + if (conv && (c->flags & GAIM_CMD_FLAG_PRPL_ONLY) && c->prpl_id && + (strcmp(c->prpl_id, gaim_account_get_protocol_id(gaim_conversation_get_account(conv))) != 0)) + continue; + + ret = g_list_append(ret, c->help); + } + + ret = g_list_sort(ret, (GCompareFunc)strcmp); + + return ret; +} + diff -r d10dda2777a9 -r b63ebf84c42b core/cmds.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/cmds.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,193 @@ +/** + * @file cmds.h Commands API + * @ingroup core + * + * Copyright (C) 2003 Timothy Ringenbach + * + * 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_CMDS_H_ +#define _GAIM_CMDS_H_ + +#include "conversation.h" + +/**************************************************************************/ +/** @name Structures */ +/**************************************************************************/ +/*@{*/ + +typedef enum _GaimCmdPriority GaimCmdPriority; +typedef enum _GaimCmdFlag GaimCmdFlag; +typedef enum _GaimCmdStatus GaimCmdStatus; +typedef enum _GaimCmdRet GaimCmdRet; + +enum _GaimCmdStatus { + GAIM_CMD_STATUS_OK, + GAIM_CMD_STATUS_FAILED, + GAIM_CMD_STATUS_NOT_FOUND, + GAIM_CMD_STATUS_WRONG_ARGS, + GAIM_CMD_STATUS_WRONG_PRPL, + GAIM_CMD_STATUS_WRONG_TYPE, +}; + +enum _GaimCmdRet { + GAIM_CMD_RET_OK, /**< Everything's okay. Don't look for another command to call. */ + GAIM_CMD_RET_FAILED, /**< The command failed, but stop looking.*/ + GAIM_CMD_RET_CONTINUE, /**< Continue, looking for other commands with the same name to call. */ +}; + +#define GAIM_CMD_FUNC(func) ((GaimCmdFunc)func) + +typedef GaimCmdRet (*GaimCmdFunc)(GaimConversation *, const gchar *cmd, + gchar **args, gchar **error, void *data); +typedef guint GaimCmdId; + +enum _GaimCmdPriority { + GAIM_CMD_P_VERY_LOW = -1000, + GAIM_CMD_P_LOW = 0, + GAIM_CMD_P_DEFAULT = 1000, + GAIM_CMD_P_PRPL = 2000, + GAIM_CMD_P_PLUGIN = 3000, + GAIM_CMD_P_ALIAS = 4000, + GAIM_CMD_P_HIGH = 5000, + GAIM_CMD_P_VERY_HIGH = 6000, +}; + +enum _GaimCmdFlag { + GAIM_CMD_FLAG_IM = 0x01, + GAIM_CMD_FLAG_CHAT = 0x02, + GAIM_CMD_FLAG_PRPL_ONLY = 0x04, + GAIM_CMD_FLAG_ALLOW_WRONG_ARGS = 0x08, +}; + + +/*@}*/ + +#ifdef __cplusplus +extern "C" { +#endif + +/**************************************************************************/ +/** @name Commands API */ +/**************************************************************************/ +/*@{*/ + +/** + * Register a new command with the core. + * + * The command will only happen if commands are enabled, + * which is a UI pref. UIs don't have to support commands at all. + * + * @param cmd The command. This should be a UTF8 (or ASCII) string, with no spaces + * or other white space. + * @param args This tells Gaim how to parse the arguments to the command for you. + * If what the user types doesn't match, Gaim will keep looking for another + * command, unless the flag @c GAIM_CMD_FLAG_ALLOW_WRONG_ARGS is passed in f. + * This string contains no whitespace, and uses a single character for each argument. + * The recognized characters are: + * 'w' Matches a single word. + * 'W' Matches a single word, with formatting. + * 's' Matches the rest of the arguments after this point, as a single string. + * 'S' Same as 's' but with formatting. + * If args is the empty string, then the command accepts no arguments. + * The args passed to callback func will be a @c NULL terminated array of null + * terminated strings, and will always match the number of arguments asked for, + * unless GAIM_CMD_FLAG_ALLOW_WRONG_ARGS is passed. + * @param p This is the priority. Higher priority commands will be run first, and usually the + * first command will stop any others from being called. + * @param f These are the flags. You need to at least pass one of GAIM_CMD_FLAG_IM or + * GAIM_CMD_FLAG_CHAT (can may pass both) in order for the command to ever actually + * be called. + * @param prpl_id This is the prpl's id string. This is only meaningful if the proper flag is set. + * @param func This is the function to call when someone enters this command. + * @param helpstr This is a whitespace sensitive, UTF-8, HTML string describing how to use the command. + * The preferred format of this string shall be the commands name, followed by a space + * and any arguments it accepts (if it takes any arguments, otherwise no space), followed + * by a colon, two spaces, and a description of the command in sentence form. No slash + * before the command name. + * @param data User defined data to pass to the GaimCmdFunc + * @return A GaimCmdId. This is only used for calling gaim_cmd_unregister. + * Returns 0 on failure. + */ +GaimCmdId gaim_cmd_register(const gchar *cmd, const gchar *args, GaimCmdPriority p, GaimCmdFlag f, + const gchar *prpl_id, GaimCmdFunc func, const gchar *helpstr, void *data); + +/** + * Unregister a command with the core. + * + * All registered commands must be unregistered, if they're registered by a plugin + * or something else that might go away. Normally this is called when the plugin + * unloads itself. + * + * @param id The GaimCmdId to unregister. + */ +void gaim_cmd_unregister(GaimCmdId id); + +/** + * Do a command. + * + * Normally the UI calls this to perform a command. This might also be useful + * if aliases are ever implemented. + * + * @param conv The conversation the command was typed in. + * @param cmdline The command the user typed (including all arguments) as a single string. + * The caller doesn't have to do any parsing, except removing the command + * prefix, which the core has no knowledge of. cmd should not contain any + * formatting, and should be in plain text (no html entities). + * @param markup This is the same as cmd, but is the formatted version. It should be in + * HTML, with < > and &, at least, escaped to html entities, and should + * include both the default formatting and any extra manual formatting. + * @param errormsg If the command failed errormsg is filled in with the appropriate error + * message. It must be freed by the caller with g_free(). + * @return A GaimCmdStatus indicated if the command succeeded or failed. + */ +GaimCmdStatus gaim_cmd_do_command(GaimConversation *conv, const gchar *cmdline, + const gchar *markup, gchar **errormsg); + +/** + * List registered commands. + * + * Returns a GList (which must be freed by the caller) of all commands + * that are valid in the context of conv, or all commands, if conv is + * @c NULL. Don't keep this list around past the main loop, or anything else + * that might unregister a command, as the char*'s used get freed then. + * + * @param conv The conversation, or @c NULL. + * @return A GList of const char*, which must be freed with g_list_free(). + */ +GList *gaim_cmd_list(GaimConversation *conv); + +/** + * Get the help string for a command. + * + * Returns the help strings for a given command in the form of a GList, + * one node for each matching command. + * + * @param conv The conversation, or @c NULL for no context. + * @param cmd The command. No wildcards accepted, but returns help for all + * commands if @c NULL. + * @return A GList of const char*s, which is the help string + * for that command. + */ +GList *gaim_cmd_help(GaimConversation *conv, const gchar *cmd); + +/*@}*/ + +#ifdef __cplusplus +} +#endif + +#endif /* _GAIM_CMDS_H_ */ diff -r d10dda2777a9 -r b63ebf84c42b core/connection.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/connection.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,515 @@ +/** + * @file connection.c Connection API + * @ingroup core + * + * 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 "account.h" +#include "blist.h" +#include "connection.h" +#include "dbus-maybe.h" +#include "debug.h" +#include "gaim.h" +#include "log.h" +#include "notify.h" +#include "prefs.h" +#include "request.h" +#include "server.h" +#include "signals.h" +#include "util.h" + +static GList *connections = NULL; +static GList *connections_connecting = NULL; +static GaimConnectionUiOps *connection_ui_ops = NULL; + +static int connections_handle; + +static gboolean +send_keepalive(gpointer data) +{ + GaimConnection *gc = data; + GaimPluginProtocolInfo *prpl_info = NULL; + + if (gc != NULL && gc->prpl != NULL) + prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl); + + if (prpl_info && prpl_info->keepalive) + prpl_info->keepalive(gc); + + return TRUE; +} + +static void +update_keepalive(GaimConnection *gc, gboolean on) +{ + if (on && !gc->keepalive) + { + gaim_debug_info("connection", "Activating keepalive.\n"); + gc->keepalive = gaim_timeout_add(30000, send_keepalive, gc); + } + else if (!on && gc->keepalive > 0) + { + gaim_debug_info("connection", "Deactivating keepalive.\n"); + gaim_timeout_remove(gc->keepalive); + gc->keepalive = 0; + } +} + +void +gaim_connection_new(GaimAccount *account, gboolean regist, const char *password) +{ + GaimConnection *gc; + GaimPlugin *prpl; + GaimPluginProtocolInfo *prpl_info; + + g_return_if_fail(account != NULL); + + if (!gaim_account_is_disconnected(account)) + return; + + prpl = gaim_find_prpl(gaim_account_get_protocol_id(account)); + + if (prpl != NULL) + prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(prpl); + else { + gchar *message; + + message = g_strdup_printf(_("Missing protocol plugin for %s"), + gaim_account_get_username(account)); + gaim_notify_error(NULL, regist ? _("Registration Error") : + _("Connection Error"), message, NULL); + g_free(message); + return; + } + + if (regist) + { + if (prpl_info->register_user == NULL) + return; + } + else + { + if (((password == NULL) || (*password == '\0')) && + !(prpl_info->options & OPT_PROTO_NO_PASSWORD) && + !(prpl_info->options & OPT_PROTO_PASSWORD_OPTIONAL)) + { + gaim_debug_error("connection", "Can not connect to account %s without " + "a password.\n", gaim_account_get_username(account)); + return; + } + } + + gc = g_new0(GaimConnection, 1); + GAIM_DBUS_REGISTER_POINTER(gc, GaimConnection); + + gc->prpl = prpl; + if ((password != NULL) && (*password != '\0')) + gc->password = g_strdup(password); + gaim_connection_set_account(gc, account); + gaim_connection_set_state(gc, GAIM_CONNECTING); + connections = g_list_append(connections, gc); + gaim_account_set_connection(account, gc); + + gaim_signal_emit(gaim_connections_get_handle(), "signing-on", gc); + + if (regist) + { + gaim_debug_info("connection", "Registering. gc = %p\n", gc); + + /* set this so we don't auto-reconnect after registering */ + gc->wants_to_die = TRUE; + + prpl_info->register_user(account); + } + else + { + gaim_debug_info("connection", "Connecting. gc = %p\n", gc); + + gaim_signal_emit(gaim_accounts_get_handle(), "account-connecting", account); + prpl_info->login(account); + } +} + +void +gaim_connection_destroy(GaimConnection *gc) +{ + GaimAccount *account; +#if 0 + GList *wins; +#endif + GaimPluginProtocolInfo *prpl_info = NULL; + gboolean remove = FALSE; + + g_return_if_fail(gc != NULL); + + account = gaim_connection_get_account(gc); + + gaim_debug_info("connection", "Disconnecting connection %p\n", gc); + + if (gaim_connection_get_state(gc) != GAIM_CONNECTING) + remove = TRUE; + + gaim_signal_emit(gaim_connections_get_handle(), "signing-off", gc); + + while (gc->buddy_chats) + { + GaimConversation *b = gc->buddy_chats->data; + + gc->buddy_chats = g_slist_remove(gc->buddy_chats, b); + gaim_conv_chat_left(GAIM_CONV_CHAT(b)); + } + + update_keepalive(gc, FALSE); + + if (gc->prpl != NULL) + { + prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl); + + if (prpl_info->close) + (prpl_info->close)(gc); + } + + connections = g_list_remove(connections, gc); + + gaim_connection_set_state(gc, GAIM_DISCONNECTED); + + if (remove) + gaim_blist_remove_account(account); + + gaim_signal_emit(gaim_connections_get_handle(), "signed-off", gc); + +#if 0 + /* see comment later in file on if 0'd same code */ + /* + * XXX This is a hack! Remove this and replace it with a better event + * notification system. + */ + for (wins = gaim_get_windows(); wins != NULL; wins = wins->next) { + GaimConvWindow *win = (GaimConvWindow *)wins->data; + gaim_conversation_update(gaim_conv_window_get_conversation_at(win, 0), + GAIM_CONV_ACCOUNT_OFFLINE); + } +#endif + + gaim_request_close_with_handle(gc); + gaim_notify_close_with_handle(gc); + + gaim_debug_info("connection", "Destroying connection %p\n", gc); + + gaim_account_set_connection(account, NULL); + + g_free(gc->password); + g_free(gc->display_name); + + if (gc->disconnect_timeout) + gaim_timeout_remove(gc->disconnect_timeout); + + GAIM_DBUS_UNREGISTER_POINTER(gc); + g_free(gc); +} + +/* + * d:)->-< + * + * d:O-\-< + * + * d:D-/-< + * + * d8D->-< DANCE! + */ + +void +gaim_connection_set_state(GaimConnection *gc, GaimConnectionState state) +{ + GaimConnectionUiOps *ops; + + g_return_if_fail(gc != NULL); + + if (gc->state == state) + return; + + gc->state = state; + + ops = gaim_connections_get_ui_ops(); + + if (gc->state == GAIM_CONNECTING) { + connections_connecting = g_list_append(connections_connecting, gc); + } + else { + connections_connecting = g_list_remove(connections_connecting, gc); + } + + if (gc->state == GAIM_CONNECTED) { + GaimAccount *account; + GaimPresence *presence; + + account = gaim_connection_get_account(gc); + presence = gaim_account_get_presence(account); + + /* Set the time the account came online */ + gaim_presence_set_login_time(presence, time(NULL)); + + if (gaim_prefs_get_bool("/core/logging/log_system")) + { + GaimLog *log = gaim_account_get_log(account, TRUE); + + if (log != NULL) + { + char *msg = g_strdup_printf(_("+++ %s signed on"), + gaim_account_get_username(account)); + gaim_log_write(log, GAIM_MESSAGE_SYSTEM, + gaim_account_get_username(account), + gaim_presence_get_login_time(presence), + msg); + g_free(msg); + } + } + + if (ops != NULL && ops->connected != NULL) + ops->connected(gc); + + gaim_blist_add_account(account); + + gaim_signal_emit(gaim_connections_get_handle(), "signed-on", gc); + + serv_set_permit_deny(gc); + + update_keepalive(gc, TRUE); + + if (gaim_account_get_user_info(account) != NULL) + serv_set_info(gc, gaim_account_get_user_info(account)); + } + else if (gc->state == GAIM_DISCONNECTED) { + GaimAccount *account = gaim_connection_get_account(gc); + + if (gaim_prefs_get_bool("/core/logging/log_system")) + { + GaimLog *log = gaim_account_get_log(account, FALSE); + + if (log != NULL) + { + char *msg = g_strdup_printf(_("+++ %s signed off"), + gaim_account_get_username(account)); + gaim_log_write(log, GAIM_MESSAGE_SYSTEM, + gaim_account_get_username(account), time(NULL), + msg); + g_free(msg); + } + } + + gaim_account_destroy_log(account); + + if (ops != NULL && ops->disconnected != NULL) + ops->disconnected(gc); + } +} + +void +gaim_connection_set_account(GaimConnection *gc, GaimAccount *account) +{ + g_return_if_fail(gc != NULL); + g_return_if_fail(account != NULL); + + gc->account = account; +} + +void +gaim_connection_set_display_name(GaimConnection *gc, const char *name) +{ + g_return_if_fail(gc != NULL); + + g_free(gc->display_name); + gc->display_name = g_strdup(name); +} + +GaimConnectionState +gaim_connection_get_state(const GaimConnection *gc) +{ + g_return_val_if_fail(gc != NULL, GAIM_DISCONNECTED); + + return gc->state; +} + +GaimAccount * +gaim_connection_get_account(const GaimConnection *gc) +{ + g_return_val_if_fail(gc != NULL, NULL); + + return gc->account; +} + +const char * +gaim_connection_get_password(const GaimConnection *gc) +{ + g_return_val_if_fail(gc != NULL, NULL); + + return gc->password; +} + +const char * +gaim_connection_get_display_name(const GaimConnection *gc) +{ + g_return_val_if_fail(gc != NULL, NULL); + + return gc->display_name; +} + +void +gaim_connection_update_progress(GaimConnection *gc, const char *text, + size_t step, size_t count) +{ + GaimConnectionUiOps *ops; + + g_return_if_fail(gc != NULL); + g_return_if_fail(text != NULL); + g_return_if_fail(step < count); + g_return_if_fail(count > 1); + + ops = gaim_connections_get_ui_ops(); + + if (ops != NULL && ops->connect_progress != NULL) + ops->connect_progress(gc, text, step, count); +} + +void +gaim_connection_notice(GaimConnection *gc, const char *text) +{ + GaimConnectionUiOps *ops; + + g_return_if_fail(gc != NULL); + g_return_if_fail(text != NULL); + + ops = gaim_connections_get_ui_ops(); + + if (ops != NULL && ops->notice != NULL) + ops->notice(gc, text); +} + +static gboolean +gaim_connection_disconnect_cb(gpointer data) +{ + GaimAccount *account = data; + char *password = g_strdup(gaim_account_get_password(account)); + gaim_account_disconnect(account); + gaim_account_set_password(account, password); + g_free(password); + return FALSE; +} + +void +gaim_connection_error(GaimConnection *gc, const char *text) +{ + GaimConnectionUiOps *ops; + + g_return_if_fail(gc != NULL); + g_return_if_fail(GAIM_CONNECTION_IS_VALID(gc)); + g_return_if_fail(text != NULL); + + /* If we've already got one error, we don't need any more */ + if (gc->disconnect_timeout) + return; + + ops = gaim_connections_get_ui_ops(); + + if (ops != NULL) { + if (ops->report_disconnect != NULL) + ops->report_disconnect(gc, text); + } + + gc->disconnect_timeout = gaim_timeout_add(0, gaim_connection_disconnect_cb, + gaim_connection_get_account(gc)); +} + +void +gaim_connections_disconnect_all(void) +{ + GList *l; + GaimConnection *gc; + + while ((l = gaim_connections_get_all()) != NULL) { + gc = l->data; + gc->wants_to_die = TRUE; + gaim_account_disconnect(gc->account); + } +} + +GList * +gaim_connections_get_all(void) +{ + return connections; +} + +GList * +gaim_connections_get_connecting(void) +{ + return connections_connecting; +} + +void +gaim_connections_set_ui_ops(GaimConnectionUiOps *ops) +{ + connection_ui_ops = ops; +} + +GaimConnectionUiOps * +gaim_connections_get_ui_ops(void) +{ + return connection_ui_ops; +} + +void +gaim_connections_init(void) +{ + void *handle = gaim_connections_get_handle(); + + gaim_signal_register(handle, "signing-on", + gaim_marshal_VOID__POINTER, NULL, 1, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_CONNECTION)); + + gaim_signal_register(handle, "signed-on", + gaim_marshal_VOID__POINTER, NULL, 1, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_CONNECTION)); + + gaim_signal_register(handle, "signing-off", + gaim_marshal_VOID__POINTER, NULL, 1, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_CONNECTION)); + + gaim_signal_register(handle, "signed-off", + gaim_marshal_VOID__POINTER, NULL, 1, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_CONNECTION)); +} + +void +gaim_connections_uninit(void) +{ + gaim_signals_unregister_by_instance(gaim_connections_get_handle()); +} + +void * +gaim_connections_get_handle(void) +{ + return &connections_handle; +} diff -r d10dda2777a9 -r b63ebf84c42b core/connection.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/connection.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,330 @@ +/** + * @file connection.h Connection API + * @ingroup core + * + * 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 + * + * @see @ref connection-signals + */ +#ifndef _GAIM_CONNECTION_H_ +#define _GAIM_CONNECTION_H_ + +typedef struct _GaimConnection GaimConnection; + +/** + * Flags to change behavior of the client for a given connection. + */ +typedef enum +{ + GAIM_CONNECTION_HTML = 0x0001, /**< Connection sends/receives in 'HTML'. */ + GAIM_CONNECTION_NO_BGCOLOR = 0x0002, /**< Connection does not send/receive + background colors. */ + GAIM_CONNECTION_AUTO_RESP = 0x0004, /**< Send auto responses when away. */ + GAIM_CONNECTION_FORMATTING_WBFO = 0x0008, /**< The text buffer must be formatted as a whole */ + GAIM_CONNECTION_NO_NEWLINES = 0x0010, /**< No new lines are allowed in outgoing messages */ + GAIM_CONNECTION_NO_FONTSIZE = 0x0020, /**< Connection does not send/receive font sizes */ + GAIM_CONNECTION_NO_URLDESC = 0x0040, /**< Connection does not support descriptions with links */ + GAIM_CONNECTION_NO_IMAGES = 0x0080, /**< Connection does not support sending of images */ + +} GaimConnectionFlags; + +typedef enum +{ + GAIM_DISCONNECTED = 0, /**< Disconnected. */ + GAIM_CONNECTED, /**< Connected. */ + GAIM_CONNECTING /**< Connecting. */ + +} GaimConnectionState; + +#include + +#include "account.h" +#include "plugin.h" +#include "status.h" + +typedef struct +{ + void (*connect_progress)(GaimConnection *gc, const char *text, + size_t step, size_t step_count); + void (*connected)(GaimConnection *gc); + void (*disconnected)(GaimConnection *gc); + void (*notice)(GaimConnection *gc, const char *text); + void (*report_disconnect)(GaimConnection *gc, const char *text); + +} GaimConnectionUiOps; + +struct _GaimConnection +{ + GaimPlugin *prpl; /**< The protocol plugin. */ + GaimConnectionFlags flags; /**< Connection flags. */ + + GaimConnectionState state; /**< The connection state. */ + + GaimAccount *account; /**< The account being connected to. */ + char *password; /**< The password used. */ + int inpa; /**< The input watcher. */ + + GSList *buddy_chats; /**< A list of active chats. */ + void *proto_data; /**< Protocol-specific data. */ + + char *display_name; /**< The name displayed. */ + guint keepalive; /**< Keep-alive. */ + + + gboolean wants_to_die; /**< Wants to Die state. This is set + when the user chooses to log out, + or when the protocol is + disconnected and should not be + automatically reconnected + (incorrect password, etc.) */ + guint disconnect_timeout; /**< Timer used for nasty stack tricks */ +}; + +#ifdef __cplusplus +extern "C" { +#endif + +/**************************************************************************/ +/** @name Connection API */ +/**************************************************************************/ +/*@{*/ + +/** + * This function should only be called by gaim_account_connect() + * in account.c. If you're trying to sign on an account, use that + * function instead. + * + * Creates a connection to the specified account and either connects + * or attempts to register a new account. If you are logging in, + * the connection uses the current active status for this account. + * So if you want to sign on as "away," for example, you need to + * have called gaim_account_set_status(account, "away"). + * (And this will call gaim_account_connect() automatically). + * + * @param account The account the connection should be connecting to. + * @param regist Whether we are registering a new account or just + * trying to do a normal signon. + * @param password The password to use. + */ +void gaim_connection_new(GaimAccount *account, gboolean regist, + const char *password); + +/** + * Disconnects and destroys a GaimConnection. + * + * This function should only be called by gaim_account_disconnect() + * in account.c. If you're trying to sign off an account, use that + * function instead. + * + * @param gc The gaim connection to destroy. + */ +void gaim_connection_destroy(GaimConnection *gc); + +/** + * Sets the connection state. PRPLs should call this and pass in + * the state "GAIM_CONNECTED" when the account is completely + * signed on. What does it mean to be completely signed on? If + * the core can call prpl->set_status, and it successfully changes + * your status, then the account is online. + * + * @param gc The connection. + * @param state The connection state. + */ +void gaim_connection_set_state(GaimConnection *gc, GaimConnectionState state); + +/** + * Sets the connection's account. + * + * @param gc The connection. + * @param account The account. + */ +void gaim_connection_set_account(GaimConnection *gc, GaimAccount *account); + +/** + * Sets the connection's displayed name. + * + * @param gc The connection. + * @param name The displayed name. + */ +void gaim_connection_set_display_name(GaimConnection *gc, const char *name); + +/** + * Returns the connection state. + * + * @param gc The connection. + * + * @return The connection state. + */ +GaimConnectionState gaim_connection_get_state(const GaimConnection *gc); + +/** + * Returns TRUE if the account is connected, otherwise returns FALSE. + * + * @return TRUE if the account is connected, otherwise returns FALSE. + */ +#define GAIM_CONNECTION_IS_CONNECTED(gc) \ + (gc->state == GAIM_CONNECTED) + +/** + * Returns the connection's account. + * + * @param gc The connection. + * + * @return The connection's account. + */ +GaimAccount *gaim_connection_get_account(const GaimConnection *gc); + +/** + * Returns the connection's password. + * + * @param gc The connection. + * + * @return The connection's password. + */ +const char *gaim_connection_get_password(const GaimConnection *gc); + +/** + * Returns the connection's displayed name. + * + * @param gc The connection. + * + * @return The connection's displayed name. + */ +const char *gaim_connection_get_display_name(const GaimConnection *gc); + +/** + * Updates the connection progress. + * + * @param gc The connection. + * @param text Information on the current step. + * @param step The current step. + * @param count The total number of steps. + */ +void gaim_connection_update_progress(GaimConnection *gc, const char *text, + size_t step, size_t count); + +/** + * Displays a connection-specific notice. + * + * @param gc The connection. + * @param text The notice text. + */ +void gaim_connection_notice(GaimConnection *gc, const char *text); + +/** + * Closes a connection with an error. + * + * @param gc The connection. + * @param reason The error text. + */ +void gaim_connection_error(GaimConnection *gc, const char *reason); + +/*@}*/ + +/**************************************************************************/ +/** @name Connections API */ +/**************************************************************************/ +/*@{*/ + +/** + * Disconnects from all connections. + */ +void gaim_connections_disconnect_all(void); + +/** + * Returns a list of all active connections. This does not + * include connections that are in the process of connecting. + * + * @return A list of all active connections. + */ +GList *gaim_connections_get_all(void); + +/** + * Returns a list of all connections in the process of connecting. + * + * @return A list of connecting connections. + */ +GList *gaim_connections_get_connecting(void); + +/** + * Checks if gc is still a valid pointer to a gc. + * + * @return @c TRUE if gc is valid. + */ +/* + * TODO: Eventually this bad boy will be removed, because it is + * a gross fix for a crashy problem. + */ +#define GAIM_CONNECTION_IS_VALID(gc) (g_list_find(gaim_connections_get_all(), (gc))) + +/*@}*/ + +/**************************************************************************/ +/** @name UI Registration Functions */ +/**************************************************************************/ +/*@{*/ + +/** + * Sets the UI operations structure to be used for connections. + * + * @param ops The UI operations structure. + */ +void gaim_connections_set_ui_ops(GaimConnectionUiOps *ops); + +/** + * Returns the UI operations structure used for connections. + * + * @return The UI operations structure in use. + */ +GaimConnectionUiOps *gaim_connections_get_ui_ops(void); + +/*@}*/ + +/**************************************************************************/ +/** @name Connections Subsystem */ +/**************************************************************************/ +/*@{*/ + +/** + * Initializes the connections subsystem. + */ +void gaim_connections_init(void); + +/** + * Uninitializes the connections subsystem. + */ +void gaim_connections_uninit(void); + +/** + * Returns the handle to the connections subsystem. + * + * @return The connections subsystem handle. + */ +void *gaim_connections_get_handle(void); + +/*@}*/ + + +#ifdef __cplusplus +} +#endif + +#endif /* _GAIM_CONNECTION_H_ */ diff -r d10dda2777a9 -r b63ebf84c42b core/conversation.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/conversation.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,2221 @@ +/* + * 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 "blist.h" +#include "conversation.h" +#include "dbus-maybe.h" +#include "debug.h" +#include "imgstore.h" +#include "notify.h" +#include "prefs.h" +#include "prpl.h" +#include "signals.h" +#include "util.h" + +#define SEND_TYPED_TIMEOUT 5000 + +static GList *conversations = NULL; +static GList *ims = NULL; +static GList *chats = NULL; +static GaimConversationUiOps *default_ops = NULL; + +void +gaim_conversations_set_ui_ops(GaimConversationUiOps *ops) +{ + default_ops = ops; +} + +static gboolean +reset_typing_cb(gpointer data) +{ + GaimConversation *c = (GaimConversation *)data; + GaimConvIm *im; + + im = GAIM_CONV_IM(c); + + gaim_conv_im_set_typing_state(im, GAIM_NOT_TYPING); + gaim_conv_im_update_typing(im); + gaim_conv_im_stop_typing_timeout(im); + + return FALSE; +} + +static gboolean +send_typed_cb(gpointer data) +{ + GaimConversation *conv = (GaimConversation *)data; + GaimConnection *gc; + const char *name; + + g_return_val_if_fail(conv != NULL, FALSE); + + gc = gaim_conversation_get_gc(conv); + name = gaim_conversation_get_name(conv); + + if (gc != NULL && name != NULL) { + /* We set this to 1 so that GAIM_TYPING will be sent + * if the Gaim user types anything else. + */ + gaim_conv_im_set_type_again(GAIM_CONV_IM(conv), 1); + + serv_send_typing(gc, name, GAIM_TYPED); + gaim_signal_emit(gaim_conversations_get_handle(), + "buddy-typed", conv->account, conv->name); + + gaim_debug(GAIM_DEBUG_MISC, "conversation", "typed...\n"); + } + + return FALSE; +} + +static void +common_send(GaimConversation *conv, const char *message, GaimMessageFlags msgflags) +{ + GaimConversationType type; + GaimAccount *account; + GaimConnection *gc; + char *displayed = NULL, *sent = NULL; + int err = 0; + + if (strlen(message) == 0) + return; + + account = gaim_conversation_get_account(conv); + gc = gaim_conversation_get_gc(conv); + + g_return_if_fail(account != NULL); + g_return_if_fail(gc != NULL); + + type = gaim_conversation_get_type(conv); + + /* Always linkfy the text for display */ + displayed = gaim_markup_linkify(message); + + if ((conv->features & GAIM_CONNECTION_HTML) && + !(msgflags & GAIM_MESSAGE_RAW)) + { + sent = g_strdup(displayed); + } + else + sent = g_strdup(message); + + msgflags |= GAIM_MESSAGE_SEND; + + if (type == GAIM_CONV_TYPE_IM) { + GaimConvIm *im = GAIM_CONV_IM(conv); + + gaim_signal_emit(gaim_conversations_get_handle(), "sending-im-msg", + account, + gaim_conversation_get_name(conv), &sent); + + if (sent != NULL && sent[0] != '\0') { + + err = serv_send_im(gc, gaim_conversation_get_name(conv), + sent, msgflags); + + if ((err > 0) && (displayed != NULL)) + gaim_conv_im_write(im, NULL, displayed, msgflags, time(NULL)); + + gaim_signal_emit(gaim_conversations_get_handle(), "sent-im-msg", + account, + gaim_conversation_get_name(conv), sent); + } + } + else { + gaim_signal_emit(gaim_conversations_get_handle(), "sending-chat-msg", + account, &sent, + gaim_conv_chat_get_id(GAIM_CONV_CHAT(conv))); + + if (sent != NULL && sent[0] != '\0') { + err = serv_chat_send(gc, gaim_conv_chat_get_id(GAIM_CONV_CHAT(conv)), sent, msgflags); + + gaim_signal_emit(gaim_conversations_get_handle(), "sent-chat-msg", + account, sent, + gaim_conv_chat_get_id(GAIM_CONV_CHAT(conv))); + } + } + + if (err < 0) { + const char *who; + const char *msg; + + who = gaim_conversation_get_name(conv); + + if (err == -E2BIG) { + msg = _("Unable to send message: The message is too large."); + + if (!gaim_conv_present_error(who, account, msg)) { + char *msg2 = g_strdup_printf(_("Unable to send message to %s."), who); + gaim_notify_error(gc, NULL, msg2, _("The message is too large.")); + g_free(msg2); + } + } + else if (err == -ENOTCONN) { + gaim_debug(GAIM_DEBUG_ERROR, "conversation", + "Not yet connected.\n"); + } + else { + msg = _("Unable to send message."); + + if (!gaim_conv_present_error(who, account, msg)) { + char *msg2 = g_strdup_printf(_("Unable to send message to %s."), who); + gaim_notify_error(gc, NULL, msg2, NULL); + g_free(msg2); + } + } + } + + g_free(displayed); + g_free(sent); +} + +static void +open_log(GaimConversation *conv) +{ + conv->logs = g_list_append(NULL, gaim_log_new(conv->type == GAIM_CONV_TYPE_CHAT ? GAIM_LOG_CHAT : + GAIM_LOG_IM, conv->name, conv->account, + conv, time(NULL), NULL)); +} + + +/************************************************************************** + * Conversation API + **************************************************************************/ +static void +gaim_conversation_chat_cleanup_for_rejoin(GaimConversation *conv) +{ + const char *disp; + GaimAccount *account; + GaimConnection *gc; + + account = gaim_conversation_get_account(conv); + + gaim_conversation_close_logs(conv); + open_log(conv); + + gc = gaim_account_get_connection(account); + + if ((disp = gaim_connection_get_display_name(gc)) != NULL) + gaim_conv_chat_set_nick(GAIM_CONV_CHAT(conv), disp); + else + { + gaim_conv_chat_set_nick(GAIM_CONV_CHAT(conv), + gaim_account_get_username(account)); + } + + gaim_conv_chat_clear_users(GAIM_CONV_CHAT(conv)); + gaim_conv_chat_set_topic(GAIM_CONV_CHAT(conv), NULL, NULL); + GAIM_CONV_CHAT(conv)->left = FALSE; + + gaim_conversation_update(conv, GAIM_CONV_UPDATE_CHATLEFT); +} + +GaimConversation * +gaim_conversation_new(GaimConversationType type, GaimAccount *account, + const char *name) +{ + GaimConversation *conv; + GaimConnection *gc; + GaimConversationUiOps *ops; + + g_return_val_if_fail(type != GAIM_CONV_TYPE_UNKNOWN, NULL); + g_return_val_if_fail(account != NULL, NULL); + g_return_val_if_fail(name != NULL, NULL); + + /* Check if this conversation already exists. */ + if ((conv = gaim_find_conversation_with_account(type, name, account)) != NULL) + { + if (gaim_conversation_get_type(conv) != GAIM_CONV_TYPE_CHAT || + gaim_conv_chat_has_left(GAIM_CONV_CHAT(conv))) + { + if (gaim_conversation_get_type(conv) == GAIM_CONV_TYPE_CHAT) + gaim_conversation_chat_cleanup_for_rejoin(conv); + + return conv; + } + } + + gc = gaim_account_get_connection(account); + g_return_val_if_fail(gc != NULL, NULL); + + conv = g_new0(GaimConversation, 1); + GAIM_DBUS_REGISTER_POINTER(conv, GaimConversation); + + conv->type = type; + conv->account = account; + conv->name = g_strdup(name); + conv->title = g_strdup(name); + conv->data = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, NULL); + /* copy features from the connection. */ + conv->features = gc->flags; + + if (type == GAIM_CONV_TYPE_IM) + { + GaimBuddyIcon *icon; + conv->u.im = g_new0(GaimConvIm, 1); + conv->u.im->conv = conv; + GAIM_DBUS_REGISTER_POINTER(conv->u.im, GaimConvIm); + + ims = g_list_append(ims, conv); + if ((icon = gaim_buddy_icons_find(account, name))) + gaim_conv_im_set_icon(conv->u.im, icon); + + if (gaim_prefs_get_bool("/core/logging/log_ims")) + { + gaim_conversation_set_logging(conv, TRUE); + open_log(conv); + } + } + else if (type == GAIM_CONV_TYPE_CHAT) + { + const char *disp; + + conv->u.chat = g_new0(GaimConvChat, 1); + conv->u.chat->conv = conv; + GAIM_DBUS_REGISTER_POINTER(conv->u.chat, GaimConvChat); + + chats = g_list_append(chats, conv); + + if ((disp = gaim_connection_get_display_name(account->gc))) + gaim_conv_chat_set_nick(conv->u.chat, disp); + else + gaim_conv_chat_set_nick(conv->u.chat, + gaim_account_get_username(account)); + + if (gaim_prefs_get_bool("/core/logging/log_chats")) + { + gaim_conversation_set_logging(conv, TRUE); + open_log(conv); + } + } + + conversations = g_list_append(conversations, conv); + + /* Auto-set the title. */ + gaim_conversation_autoset_title(conv); + + /* Don't move this.. it needs to be one of the last things done otherwise + * it causes mysterious crashes on my system. + * -- Gary + */ + ops = conv->ui_ops = default_ops; + if (ops != NULL && ops->create_conversation != NULL) + ops->create_conversation(conv); + + gaim_signal_emit(gaim_conversations_get_handle(), + "conversation-created", conv); + + return conv; +} + +void +gaim_conversation_destroy(GaimConversation *conv) +{ + GaimPluginProtocolInfo *prpl_info = NULL; + GaimConversationUiOps *ops; + GaimConnection *gc; + const char *name; + + g_return_if_fail(conv != NULL); + + ops = gaim_conversation_get_ui_ops(conv); + gc = gaim_conversation_get_gc(conv); + name = gaim_conversation_get_name(conv); + + if (gc != NULL) + { + /* Still connected */ + prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl); + + if (gaim_conversation_get_type(conv) == GAIM_CONV_TYPE_IM) + { + if (gaim_prefs_get_bool("/core/conversations/im/send_typing")) + serv_send_typing(gc, name, GAIM_NOT_TYPING); + + if (gc && prpl_info->convo_closed != NULL) + prpl_info->convo_closed(gc, name); + } + else if (gaim_conversation_get_type(conv) == GAIM_CONV_TYPE_CHAT) + { + int chat_id = gaim_conv_chat_get_id(GAIM_CONV_CHAT(conv)); +#if 0 + /* + * This is unfortunately necessary, because calling + * serv_chat_leave() calls this gaim_conversation_destroy(), + * which leads to two calls here.. We can't just return after + * this, because then it'll return on the next pass. So, since + * serv_got_chat_left(), which is eventually called from the + * prpl that serv_chat_leave() calls, removes this conversation + * from the gc's buddy_chats list, we're going to check to see + * if this exists in the list. If so, we want to return after + * calling this, because it'll be called again. If not, fall + * through, because it'll have already been removed, and we'd + * be on the 2nd pass. + * + * Long paragraph. <-- Short sentence. + * + * -- ChipX86 + */ + + if (gc && g_slist_find(gc->buddy_chats, conv) != NULL) { + serv_chat_leave(gc, chat_id); + + return; + } +#endif + /* + * Instead of all of that, lets just close the window when + * the user tells us to, and let the prpl deal with the + * internals on it's own time. Don't do this if the prpl already + * knows it left the chat. + */ + if (!gaim_conv_chat_has_left(GAIM_CONV_CHAT(conv))) + serv_chat_leave(gc, chat_id); + + /* + * If they didn't call serv_got_chat_left by now, it's too late. + * So we better do it for them before we destroy the thing. + */ + if (!gaim_conv_chat_has_left(GAIM_CONV_CHAT(conv))) + serv_got_chat_left(gc, chat_id); + } + } + + /* remove from conversations and im/chats lists prior to emit */ + conversations = g_list_remove(conversations, conv); + + if(conv->type==GAIM_CONV_TYPE_IM) + ims = g_list_remove(ims, conv); + else if(conv->type==GAIM_CONV_TYPE_CHAT) + chats = g_list_remove(chats, conv); + + gaim_signal_emit(gaim_conversations_get_handle(), + "deleting-conversation", conv); + + g_free(conv->name); + g_free(conv->title); + + conv->name = NULL; + conv->title = NULL; + + if (conv->type == GAIM_CONV_TYPE_IM) { + gaim_conv_im_stop_typing_timeout(conv->u.im); + gaim_conv_im_stop_send_typed_timeout(conv->u.im); + + if (conv->u.im->icon != NULL) + gaim_buddy_icon_unref(conv->u.im->icon); + conv->u.im->icon = NULL; + + GAIM_DBUS_UNREGISTER_POINTER(conv->u.im); + g_free(conv->u.im); + conv->u.im = NULL; + } + else if (conv->type == GAIM_CONV_TYPE_CHAT) { + + g_list_foreach(conv->u.chat->in_room, (GFunc)gaim_conv_chat_cb_destroy, NULL); + g_list_free(conv->u.chat->in_room); + + g_list_foreach(conv->u.chat->ignored, (GFunc)g_free, NULL); + g_list_free(conv->u.chat->ignored); + + conv->u.chat->in_room = NULL; + conv->u.chat->ignored = NULL; + + g_free(conv->u.chat->who); + conv->u.chat->who = NULL; + + g_free(conv->u.chat->topic); + conv->u.chat->topic = NULL; + + g_free(conv->u.chat->nick); + + GAIM_DBUS_UNREGISTER_POINTER(conv->u.chat); + g_free(conv->u.chat); + conv->u.chat = NULL; + } + + g_hash_table_destroy(conv->data); + conv->data = NULL; + + if (ops != NULL && ops->destroy_conversation != NULL) + ops->destroy_conversation(conv); + + gaim_conversation_close_logs(conv); + + GAIM_DBUS_UNREGISTER_POINTER(conv); + g_free(conv); + conv = NULL; +} + + +void +gaim_conversation_present(GaimConversation *conv) { + GaimConversationUiOps *ops; + + g_return_if_fail(conv != NULL); + + ops = gaim_conversation_get_ui_ops(conv); + if(ops && ops->present) + ops->present(conv); +} + + +void +gaim_conversation_set_features(GaimConversation *conv, GaimConnectionFlags features) +{ + g_return_if_fail(conv != NULL); + + conv->features = features; + + gaim_conversation_update(conv, GAIM_CONV_UPDATE_FEATURES); +} + + +GaimConnectionFlags +gaim_conversation_get_features(GaimConversation *conv) +{ + g_return_val_if_fail(conv != NULL, 0); + return conv->features; +} + + +GaimConversationType +gaim_conversation_get_type(const GaimConversation *conv) +{ + g_return_val_if_fail(conv != NULL, GAIM_CONV_TYPE_UNKNOWN); + + return conv->type; +} + +void +gaim_conversation_set_ui_ops(GaimConversation *conv, + GaimConversationUiOps *ops) +{ + g_return_if_fail(conv != NULL); + + if (conv->ui_ops == ops) + return; + + if (conv->ui_ops != NULL && conv->ui_ops->destroy_conversation != NULL) + conv->ui_ops->destroy_conversation(conv); + + conv->ui_data = NULL; + + conv->ui_ops = ops; +} + +GaimConversationUiOps * +gaim_conversation_get_ui_ops(const GaimConversation *conv) +{ + g_return_val_if_fail(conv != NULL, NULL); + + return conv->ui_ops; +} + +void +gaim_conversation_set_account(GaimConversation *conv, GaimAccount *account) +{ + g_return_if_fail(conv != NULL); + + if (account == gaim_conversation_get_account(conv)) + return; + + conv->account = account; + + gaim_conversation_update(conv, GAIM_CONV_UPDATE_ACCOUNT); +} + +GaimAccount * +gaim_conversation_get_account(const GaimConversation *conv) +{ + g_return_val_if_fail(conv != NULL, NULL); + + return conv->account; +} + +GaimConnection * +gaim_conversation_get_gc(const GaimConversation *conv) +{ + GaimAccount *account; + + g_return_val_if_fail(conv != NULL, NULL); + + account = gaim_conversation_get_account(conv); + + if (account == NULL) + return NULL; + + return account->gc; +} + +void +gaim_conversation_set_title(GaimConversation *conv, const char *title) +{ + g_return_if_fail(conv != NULL); + g_return_if_fail(title != NULL); + + g_free(conv->title); + conv->title = g_strdup(title); + + gaim_conversation_update(conv, GAIM_CONV_UPDATE_TITLE); +} + +const char * +gaim_conversation_get_title(const GaimConversation *conv) +{ + g_return_val_if_fail(conv != NULL, NULL); + + return conv->title; +} + +void +gaim_conversation_autoset_title(GaimConversation *conv) +{ + GaimAccount *account; + GaimBuddy *b; + GaimChat *chat; + const char *text = NULL, *name; + + g_return_if_fail(conv != NULL); + + account = gaim_conversation_get_account(conv); + name = gaim_conversation_get_name(conv); + + if(gaim_conversation_get_type(conv) == GAIM_CONV_TYPE_IM) { + if(account && ((b = gaim_find_buddy(account, name)) != NULL)) + text = gaim_buddy_get_contact_alias(b); + } else if(gaim_conversation_get_type(conv) == GAIM_CONV_TYPE_CHAT) { + if(account && ((chat = gaim_blist_find_chat(account, name)) != NULL)) + text = chat->alias; + } + + + if(text == NULL) + text = name; + + gaim_conversation_set_title(conv, text); +} + +void +gaim_conversation_foreach(void (*func)(GaimConversation *conv)) +{ + GaimConversation *conv; + GList *l; + + g_return_if_fail(func != NULL); + + for (l = gaim_get_conversations(); l != NULL; l = l->next) { + conv = (GaimConversation *)l->data; + + func(conv); + } +} + +void +gaim_conversation_set_name(GaimConversation *conv, const char *name) +{ + g_return_if_fail(conv != NULL); + + g_free(conv->name); + conv->name = g_strdup(name); + + gaim_conversation_autoset_title(conv); +} + +const char * +gaim_conversation_get_name(const GaimConversation *conv) +{ + g_return_val_if_fail(conv != NULL, NULL); + + return conv->name; +} + +void +gaim_conversation_set_logging(GaimConversation *conv, gboolean log) +{ + g_return_if_fail(conv != NULL); + + if (conv->logging != log) + { + conv->logging = log; + gaim_conversation_update(conv, GAIM_CONV_UPDATE_LOGGING); + } +} + +gboolean +gaim_conversation_is_logging(const GaimConversation *conv) +{ + g_return_val_if_fail(conv != NULL, FALSE); + + return conv->logging; +} + +void +gaim_conversation_close_logs(GaimConversation *conv) +{ + g_return_if_fail(conv != NULL); + + g_list_foreach(conv->logs, (GFunc)gaim_log_free, NULL); + g_list_free(conv->logs); + conv->logs = NULL; +} + +GaimConvIm * +gaim_conversation_get_im_data(const GaimConversation *conv) +{ + g_return_val_if_fail(conv != NULL, NULL); + + if (gaim_conversation_get_type(conv) != GAIM_CONV_TYPE_IM) + return NULL; + + return conv->u.im; +} + +GaimConvChat * +gaim_conversation_get_chat_data(const GaimConversation *conv) +{ + g_return_val_if_fail(conv != NULL, NULL); + + if (gaim_conversation_get_type(conv) != GAIM_CONV_TYPE_CHAT) + return NULL; + + return conv->u.chat; +} + +void +gaim_conversation_set_data(GaimConversation *conv, const char *key, + gpointer data) +{ + g_return_if_fail(conv != NULL); + g_return_if_fail(key != NULL); + + g_hash_table_replace(conv->data, g_strdup(key), data); +} + +gpointer +gaim_conversation_get_data(GaimConversation *conv, const char *key) +{ + g_return_val_if_fail(conv != NULL, NULL); + g_return_val_if_fail(key != NULL, NULL); + + return g_hash_table_lookup(conv->data, key); +} + +GList * +gaim_get_conversations(void) +{ + return conversations; +} + +GList * +gaim_get_ims(void) +{ + return ims; +} + +GList * +gaim_get_chats(void) +{ + return chats; +} + + +GaimConversation * +gaim_find_conversation_with_account(GaimConversationType type, + const char *name, + const GaimAccount *account) +{ + GaimConversation *c = NULL; + gchar *name1; + const gchar *name2; + GList *cnv; + + g_return_val_if_fail(name != NULL, NULL); + + name1 = g_strdup(gaim_normalize(account, name)); + + for (cnv = gaim_get_conversations(); cnv != NULL; cnv = cnv->next) { + c = (GaimConversation *)cnv->data; + name2 = gaim_normalize(account, gaim_conversation_get_name(c)); + + if (((type == GAIM_CONV_TYPE_ANY) || (type == gaim_conversation_get_type(c))) && + (account == gaim_conversation_get_account(c)) && + !gaim_utf8_strcasecmp(name1, name2)) { + + break; + } + + c = NULL; + } + + g_free(name1); + + return c; +} + +void +gaim_conversation_write(GaimConversation *conv, const char *who, + const char *message, GaimMessageFlags flags, + time_t mtime) +{ + GaimPluginProtocolInfo *prpl_info = NULL; + GaimConnection *gc = NULL; + GaimAccount *account; + GaimConversationUiOps *ops; + const char *alias; + char *displayed = NULL; + GaimBuddy *b; + int plugin_return; + GaimConversationType type; + /* int logging_font_options = 0; */ + + g_return_if_fail(conv != NULL); + g_return_if_fail(message != NULL); + + ops = gaim_conversation_get_ui_ops(conv); + + if (ops == NULL || ops->write_conv == NULL) + return; + + account = gaim_conversation_get_account(conv); + type = gaim_conversation_get_type(conv); + + if (account != NULL) + gc = gaim_account_get_connection(account); + + if (gaim_conversation_get_type(conv) == GAIM_CONV_TYPE_CHAT && + (gc == NULL || !g_slist_find(gc->buddy_chats, conv))) + return; + + if (gaim_conversation_get_type(conv) == GAIM_CONV_TYPE_IM && + !g_list_find(gaim_get_conversations(), conv)) + return; + + displayed = g_strdup(message); + + plugin_return = + GPOINTER_TO_INT(gaim_signal_emit_return_1( + gaim_conversations_get_handle(), + (type == GAIM_CONV_TYPE_IM ? "writing-im-msg" : "writing-chat-msg"), + account, who, &displayed, conv, flags)); + + if (displayed == NULL) + return; + + if (plugin_return) { + g_free(displayed); + return; + } + + if (who == NULL || *who == '\0') + who = gaim_conversation_get_name(conv); + + alias = who; + + if (account != NULL) { + prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(gaim_find_prpl(gaim_account_get_protocol_id(account))); + + if (gaim_conversation_get_type(conv) == GAIM_CONV_TYPE_IM || + !(prpl_info->options & OPT_PROTO_UNIQUE_CHATNAME)) { + + if (flags & GAIM_MESSAGE_SEND) { + b = gaim_find_buddy(account, + gaim_account_get_username(account)); + + if (gaim_account_get_alias(account) != NULL) + alias = account->alias; + else if (b != NULL && strcmp(b->name, gaim_buddy_get_contact_alias(b))) + alias = gaim_buddy_get_contact_alias(b); + else if (gaim_connection_get_display_name(gc) != NULL) + alias = gaim_connection_get_display_name(gc); + else + alias = gaim_account_get_username(account); + } + else + { + b = gaim_find_buddy(account, who); + + if (b != NULL) + alias = gaim_buddy_get_contact_alias(b); + } + } + } + + if (!(flags & GAIM_MESSAGE_NO_LOG) && gaim_conversation_is_logging(conv)) { + GList *log; + + if (conv->logs == NULL) + open_log(conv); + + log = conv->logs; + while (log != NULL) { + gaim_log_write((GaimLog *)log->data, flags, alias, mtime, displayed); + log = log->next; + } + } + + if (gaim_conversation_get_type(conv) == GAIM_CONV_TYPE_IM) { + if ((flags & GAIM_MESSAGE_RECV) == GAIM_MESSAGE_RECV) { + gaim_conv_im_set_typing_state(GAIM_CONV_IM(conv), GAIM_NOT_TYPING); + } + } + + ops->write_conv(conv, who, alias, displayed, flags, mtime); + + gaim_signal_emit(gaim_conversations_get_handle(), + (type == GAIM_CONV_TYPE_IM ? "wrote-im-msg" : "wrote-chat-msg"), + account, who, displayed, conv, flags); + + g_free(displayed); +} + +gboolean +gaim_conversation_has_focus(GaimConversation *conv) +{ + gboolean ret = FALSE; + GaimConversationUiOps *ops; + + g_return_val_if_fail(conv != NULL, FALSE); + + ops = gaim_conversation_get_ui_ops(conv); + + if (ops != NULL && ops->has_focus != NULL) + ret = ops->has_focus(conv); + + return ret; +} + +/* + * TODO: Need to make sure calls to this function happen in the core + * instead of the UI. That way UIs have less work to do, and the + * core/UI split is cleaner. Also need to make sure this is called + * when chats are added/removed from the blist. + */ +void +gaim_conversation_update(GaimConversation *conv, GaimConvUpdateType type) +{ + g_return_if_fail(conv != NULL); + + gaim_signal_emit(gaim_conversations_get_handle(), + "conversation-updated", conv, type); +} + +/************************************************************************** + * IM Conversation API + **************************************************************************/ +GaimConversation * +gaim_conv_im_get_conversation(const GaimConvIm *im) +{ + g_return_val_if_fail(im != NULL, NULL); + + return im->conv; +} + +void +gaim_conv_im_set_icon(GaimConvIm *im, GaimBuddyIcon *icon) +{ + g_return_if_fail(im != NULL); + + if (im->icon != icon) + { + if (im->icon != NULL) + gaim_buddy_icon_unref(im->icon); + + im->icon = (icon == NULL ? NULL : gaim_buddy_icon_ref(icon)); + } + + gaim_conversation_update(gaim_conv_im_get_conversation(im), + GAIM_CONV_UPDATE_ICON); +} + +GaimBuddyIcon * +gaim_conv_im_get_icon(const GaimConvIm *im) +{ + g_return_val_if_fail(im != NULL, NULL); + + return im->icon; +} + +void +gaim_conv_im_set_typing_state(GaimConvIm *im, GaimTypingState state) +{ + g_return_if_fail(im != NULL); + + if (im->typing_state != state) + { + im->typing_state = state; + + if (state == GAIM_TYPING) + { + gaim_signal_emit(gaim_conversations_get_handle(), + "buddy-typing", im->conv->account, im->conv->name); + } + else if (state == GAIM_TYPED) + { + gaim_signal_emit(gaim_conversations_get_handle(), + "buddy-typed", im->conv->account, im->conv->name); + } + else if (state == GAIM_NOT_TYPING) + { + gaim_signal_emit(gaim_conversations_get_handle(), + "buddy-typing-stopped", im->conv->account, im->conv->name); + } + } +} + +GaimTypingState +gaim_conv_im_get_typing_state(const GaimConvIm *im) +{ + g_return_val_if_fail(im != NULL, 0); + + return im->typing_state; +} + +void +gaim_conv_im_start_typing_timeout(GaimConvIm *im, int timeout) +{ + GaimConversation *conv; + const char *name; + + g_return_if_fail(im != NULL); + + if (im->typing_timeout > 0) + gaim_conv_im_stop_typing_timeout(im); + + conv = gaim_conv_im_get_conversation(im); + name = gaim_conversation_get_name(conv); + + im->typing_timeout = gaim_timeout_add(timeout * 1000, reset_typing_cb, conv); +} + +void +gaim_conv_im_stop_typing_timeout(GaimConvIm *im) +{ + g_return_if_fail(im != NULL); + + if (im->typing_timeout == 0) + return; + + gaim_timeout_remove(im->typing_timeout); + im->typing_timeout = 0; +} + +guint +gaim_conv_im_get_typing_timeout(const GaimConvIm *im) +{ + g_return_val_if_fail(im != NULL, 0); + + return im->typing_timeout; +} + +void +gaim_conv_im_set_type_again(GaimConvIm *im, unsigned int val) +{ + g_return_if_fail(im != NULL); + + if (val == 0) + im->type_again = 0; + else + im->type_again = time(NULL) + val; +} + +time_t +gaim_conv_im_get_type_again(const GaimConvIm *im) +{ + g_return_val_if_fail(im != NULL, 0); + + return im->type_again; +} + +void +gaim_conv_im_start_send_typed_timeout(GaimConvIm *im) +{ + g_return_if_fail(im != NULL); + + im->send_typed_timeout = gaim_timeout_add(SEND_TYPED_TIMEOUT, send_typed_cb, + gaim_conv_im_get_conversation(im)); +} + +void +gaim_conv_im_stop_send_typed_timeout(GaimConvIm *im) +{ + g_return_if_fail(im != NULL); + + if (im->send_typed_timeout == 0) + return; + + gaim_timeout_remove(im->send_typed_timeout); + im->send_typed_timeout = 0; +} + +guint +gaim_conv_im_get_send_typed_timeout(const GaimConvIm *im) +{ + g_return_val_if_fail(im != NULL, 0); + + return im->send_typed_timeout; +} + +void +gaim_conv_im_update_typing(GaimConvIm *im) +{ + g_return_if_fail(im != NULL); + + gaim_conversation_update(gaim_conv_im_get_conversation(im), + GAIM_CONV_UPDATE_TYPING); +} + +void +gaim_conv_im_write(GaimConvIm *im, const char *who, const char *message, + GaimMessageFlags flags, time_t mtime) +{ + GaimConversation *c; + + g_return_if_fail(im != NULL); + g_return_if_fail(message != NULL); + + c = gaim_conv_im_get_conversation(im); + + /* Raise the window, if specified in prefs. */ + if (c->ui_ops != NULL && c->ui_ops->write_im != NULL) + c->ui_ops->write_im(c, who, message, flags, mtime); + else + gaim_conversation_write(c, who, message, flags, mtime); +} + +gboolean gaim_conv_present_error(const char *who, GaimAccount *account, const char *what) +{ + GaimConversation *conv; + + g_return_val_if_fail(who != NULL, FALSE); + g_return_val_if_fail(account !=NULL, FALSE); + g_return_val_if_fail(what != NULL, FALSE); + + conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_ANY, who, account); + if (conv != NULL) + gaim_conversation_write(conv, NULL, what, GAIM_MESSAGE_ERROR, time(NULL)); + else + return FALSE; + + return TRUE; +} + +void +gaim_conv_im_send(GaimConvIm *im, const char *message) +{ + gaim_conv_im_send_with_flags(im, message, 0); +} + +void +gaim_conv_im_send_with_flags(GaimConvIm *im, const char *message, GaimMessageFlags flags) +{ + g_return_if_fail(im != NULL); + g_return_if_fail(message != NULL); + + common_send(gaim_conv_im_get_conversation(im), message, flags); +} + +gboolean +gaim_conv_custom_smiley_add(GaimConversation *conv, const char *smile, + const char *cksum_type, const char *chksum, + gboolean remote) +{ + if (conv == NULL || smile == NULL || !*smile) { + return FALSE; + } + + /* TODO: check if the icon is in the cache and return false if so */ + /* TODO: add an icon cache (that doesn't suck) */ + if (conv->ui_ops != NULL && conv->ui_ops->custom_smiley_add !=NULL) { + return conv->ui_ops->custom_smiley_add(conv, smile, remote); + } else { + gaim_debug_info("conversation", "Could not find add custom smiley function"); + return FALSE; + } + +} + +void +gaim_conv_custom_smiley_write(GaimConversation *conv, const char *smile, + const guchar *data, gsize size) +{ + g_return_if_fail(conv != NULL); + g_return_if_fail(smile != NULL && *smile); + + if (conv->ui_ops != NULL && conv->ui_ops->custom_smiley_write != NULL) + conv->ui_ops->custom_smiley_write(conv, smile, data, size); + else + gaim_debug_info("conversation", "Could not find the smiley write function"); +} + +void +gaim_conv_custom_smiley_close(GaimConversation *conv, const char *smile) +{ + g_return_if_fail(conv != NULL); + g_return_if_fail(smile != NULL && *smile); + + if (conv->ui_ops != NULL && conv->ui_ops->custom_smiley_close != NULL) + conv->ui_ops->custom_smiley_close(conv, smile); + else + gaim_debug_info("conversation", "Could not find custom smiley close function"); +} + + +/************************************************************************** + * Chat Conversation API + **************************************************************************/ + +GaimConversation * +gaim_conv_chat_get_conversation(const GaimConvChat *chat) +{ + g_return_val_if_fail(chat != NULL, NULL); + + return chat->conv; +} + +GList * +gaim_conv_chat_set_users(GaimConvChat *chat, GList *users) +{ + g_return_val_if_fail(chat != NULL, NULL); + + chat->in_room = users; + + return users; +} + +GList * +gaim_conv_chat_get_users(const GaimConvChat *chat) +{ + g_return_val_if_fail(chat != NULL, NULL); + + return chat->in_room; +} + +void +gaim_conv_chat_ignore(GaimConvChat *chat, const char *name) +{ + g_return_if_fail(chat != NULL); + g_return_if_fail(name != NULL); + + /* Make sure the user isn't already ignored. */ + if (gaim_conv_chat_is_user_ignored(chat, name)) + return; + + gaim_conv_chat_set_ignored(chat, + g_list_append(gaim_conv_chat_get_ignored(chat), g_strdup(name))); +} + +void +gaim_conv_chat_unignore(GaimConvChat *chat, const char *name) +{ + GList *item; + + g_return_if_fail(chat != NULL); + g_return_if_fail(name != NULL); + + /* Make sure the user is actually ignored. */ + if (!gaim_conv_chat_is_user_ignored(chat, name)) + return; + + item = g_list_find(gaim_conv_chat_get_ignored(chat), + gaim_conv_chat_get_ignored_user(chat, name)); + + gaim_conv_chat_set_ignored(chat, + g_list_remove_link(gaim_conv_chat_get_ignored(chat), item)); + + g_free(item->data); + g_list_free_1(item); +} + +GList * +gaim_conv_chat_set_ignored(GaimConvChat *chat, GList *ignored) +{ + g_return_val_if_fail(chat != NULL, NULL); + + chat->ignored = ignored; + + return ignored; +} + +GList * +gaim_conv_chat_get_ignored(const GaimConvChat *chat) +{ + g_return_val_if_fail(chat != NULL, NULL); + + return chat->ignored; +} + +const char * +gaim_conv_chat_get_ignored_user(const GaimConvChat *chat, const char *user) +{ + GList *ignored; + + g_return_val_if_fail(chat != NULL, NULL); + g_return_val_if_fail(user != NULL, NULL); + + for (ignored = gaim_conv_chat_get_ignored(chat); + ignored != NULL; + ignored = ignored->next) { + + const char *ign = (const char *)ignored->data; + + if (!gaim_utf8_strcasecmp(user, ign) || + ((*ign == '+' || *ign == '%') && !gaim_utf8_strcasecmp(user, ign + 1))) + return ign; + + if (*ign == '@') { + ign++; + + if ((*ign == '+' && !gaim_utf8_strcasecmp(user, ign + 1)) || + (*ign != '+' && !gaim_utf8_strcasecmp(user, ign))) + return ign; + } + } + + return NULL; +} + +gboolean +gaim_conv_chat_is_user_ignored(const GaimConvChat *chat, const char *user) +{ + g_return_val_if_fail(chat != NULL, FALSE); + g_return_val_if_fail(user != NULL, FALSE); + + return (gaim_conv_chat_get_ignored_user(chat, user) != NULL); +} + +void +gaim_conv_chat_set_topic(GaimConvChat *chat, const char *who, const char *topic) +{ + g_return_if_fail(chat != NULL); + + g_free(chat->who); + g_free(chat->topic); + + chat->who = g_strdup(who); + chat->topic = g_strdup(topic); + + gaim_conversation_update(gaim_conv_chat_get_conversation(chat), + GAIM_CONV_UPDATE_TOPIC); + + gaim_signal_emit(gaim_conversations_get_handle(), "chat-topic-changed", + chat->conv, chat->who, chat->topic); +} + +const char * +gaim_conv_chat_get_topic(const GaimConvChat *chat) +{ + g_return_val_if_fail(chat != NULL, NULL); + + return chat->topic; +} + +void +gaim_conv_chat_set_id(GaimConvChat *chat, int id) +{ + g_return_if_fail(chat != NULL); + + chat->id = id; +} + +int +gaim_conv_chat_get_id(const GaimConvChat *chat) +{ + g_return_val_if_fail(chat != NULL, -1); + + return chat->id; +} + +void +gaim_conv_chat_write(GaimConvChat *chat, const char *who, const char *message, + GaimMessageFlags flags, time_t mtime) +{ + GaimAccount *account; + GaimConversation *conv; + GaimConnection *gc; + GaimPluginProtocolInfo *prpl_info; + + g_return_if_fail(chat != NULL); + g_return_if_fail(who != NULL); + g_return_if_fail(message != NULL); + + conv = gaim_conv_chat_get_conversation(chat); + gc = gaim_conversation_get_gc(conv); + account = gaim_connection_get_account(gc); + prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl); + + /* Don't display this if the person who wrote it is ignored. */ + if (gaim_conv_chat_is_user_ignored(chat, who)) + return; + + if (!(flags & GAIM_MESSAGE_WHISPER)) { + char *str; + + str = g_strdup(gaim_normalize(account, who)); + + if (!strcmp(str, gaim_normalize(account, chat->nick))) { + flags |= GAIM_MESSAGE_SEND; + } else { + flags |= GAIM_MESSAGE_RECV; + + if (gaim_utf8_has_word(message, chat->nick)) + flags |= GAIM_MESSAGE_NICK; + } + + g_free(str); + } + + /* Pass this on to either the ops structure or the default write func. */ + if (conv->ui_ops != NULL && conv->ui_ops->write_chat != NULL) + conv->ui_ops->write_chat(conv, who, message, flags, mtime); + else + gaim_conversation_write(conv, who, message, flags, mtime); +} + +void +gaim_conv_chat_send(GaimConvChat *chat, const char *message) +{ + gaim_conv_chat_send_with_flags(chat, message, 0); +} + +void +gaim_conv_chat_send_with_flags(GaimConvChat *chat, const char *message, GaimMessageFlags flags) +{ + g_return_if_fail(chat != NULL); + g_return_if_fail(message != NULL); + + common_send(gaim_conv_chat_get_conversation(chat), message, flags); +} + +void +gaim_conv_chat_add_user(GaimConvChat *chat, const char *user, + const char *extra_msg, GaimConvChatBuddyFlags flags, + gboolean new_arrival) +{ + GList *users = g_list_append(NULL, (char *)user); + GList *extra_msgs = g_list_append(NULL, (char *)extra_msg); + GList *flags2 = g_list_append(NULL, GINT_TO_POINTER(flags)); + + gaim_conv_chat_add_users(chat, users, extra_msgs, flags2, new_arrival); + + g_list_free(users); + g_list_free(extra_msgs); + g_list_free(flags2); +} + +static int +gaim_conv_chat_cb_compare(GaimConvChatBuddy *a, GaimConvChatBuddy *b) +{ + GaimConvChatBuddyFlags f1 = 0, f2 = 0; + char *user1 = NULL, *user2 = NULL; + gint ret = 0; + + if (a) { + f1 = a->flags; + if (a->alias_key) + user1 = a->alias_key; + else if (a->name) + user1 = a->name; + } + + if (b) { + f2 = b->flags; + if (b->alias_key) + user2 = b->alias_key; + else if (b->name) + user2 = b->name; + } + + if (user1 == NULL || user2 == NULL) { + if (!(user1 == NULL && user2 == NULL)) + ret = (user1 == NULL) ? -1: 1; + } else if (f1 != f2) { + /* sort more important users first */ + ret = (f1 > f2) ? -1 : 1; + } else if (a->buddy != b->buddy) { + ret = a->buddy ? -1 : 1; + } else { + ret = strcasecmp(user1, user2); + } + + return ret; +} + +void +gaim_conv_chat_add_users(GaimConvChat *chat, GList *users, GList *extra_msgs, + GList *flags, gboolean new_arrivals) +{ + GaimConversation *conv; + GaimConversationUiOps *ops; + GaimConvChatBuddy *cbuddy; + GaimConnection *gc; + GaimPluginProtocolInfo *prpl_info; + GList *ul, *fl; + GList *cbuddies = NULL; + + g_return_if_fail(chat != NULL); + g_return_if_fail(users != NULL); + + conv = gaim_conv_chat_get_conversation(chat); + ops = gaim_conversation_get_ui_ops(conv); + + gc = gaim_conversation_get_gc(conv); + g_return_if_fail(gc != NULL); + prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl); + g_return_if_fail(prpl_info != NULL); + + ul = users; + fl = flags; + while ((ul != NULL) && (fl != NULL)) { + const char *user = (const char *)ul->data; + const char *alias = user; + gboolean quiet; + GaimConvChatBuddyFlags flag = GPOINTER_TO_INT(fl->data); + const char *extra_msg = (extra_msgs ? extra_msgs->data : NULL); + + if (!strcmp(chat->nick, gaim_normalize(conv->account, user))) { + const char *alias2 = gaim_account_get_alias(conv->account); + if (alias2 != NULL) + alias = alias2; + else + { + const char *display_name = gaim_connection_get_display_name(gc); + if (display_name != NULL) + alias = display_name; + } + } else if (!(prpl_info->options & OPT_PROTO_UNIQUE_CHATNAME)) { + GaimBuddy *buddy; + if ((buddy = gaim_find_buddy(gc->account, user)) != NULL) + alias = gaim_buddy_get_contact_alias(buddy); + } + + quiet = GPOINTER_TO_INT(gaim_signal_emit_return_1(gaim_conversations_get_handle(), + "chat-buddy-joining", conv, user, flag)) | + gaim_conv_chat_is_user_ignored(chat, user); + + cbuddy = gaim_conv_chat_cb_new(user, alias, flag); + /* This seems dumb. Why should we set users thousands of times? */ + gaim_conv_chat_set_users(chat, + g_list_prepend(gaim_conv_chat_get_users(chat), cbuddy)); + + cbuddies = g_list_prepend(cbuddies, cbuddy); + + if (!quiet && new_arrivals) { + char *escaped = g_markup_escape_text(alias, -1); + char *tmp; + + if (extra_msg == NULL) + tmp = g_strdup_printf(_("%s entered the room."), escaped); + else { + char *escaped2 = g_markup_escape_text(extra_msg, -1); + tmp = g_strdup_printf(_("%s [%s] entered the room."), + escaped, escaped2); + g_free(escaped2); + } + g_free(escaped); + + gaim_conversation_write(conv, NULL, tmp, GAIM_MESSAGE_SYSTEM, time(NULL)); + g_free(tmp); + } + + gaim_signal_emit(gaim_conversations_get_handle(), + "chat-buddy-joined", conv, user, flag, new_arrivals); + ul = ul->next; + fl = fl->next; + if (extra_msgs != NULL) + extra_msgs = extra_msgs->next; + } + + cbuddies = g_list_sort(cbuddies, (GCompareFunc)gaim_conv_chat_cb_compare); + + if (ops != NULL && ops->chat_add_users != NULL) + ops->chat_add_users(conv, cbuddies, new_arrivals); + + g_list_free(cbuddies); +} + +void +gaim_conv_chat_rename_user(GaimConvChat *chat, const char *old_user, + const char *new_user) +{ + GaimConversation *conv; + GaimConversationUiOps *ops; + GaimConnection *gc; + GaimPluginProtocolInfo *prpl_info; + GaimConvChatBuddy *cb; + GaimConvChatBuddyFlags flags; + const char *new_alias = new_user; + char tmp[BUF_LONG]; + gboolean is_me = FALSE; + + g_return_if_fail(chat != NULL); + g_return_if_fail(old_user != NULL); + g_return_if_fail(new_user != NULL); + + conv = gaim_conv_chat_get_conversation(chat); + ops = gaim_conversation_get_ui_ops(conv); + + gc = gaim_conversation_get_gc(conv); + g_return_if_fail(gc != NULL); + prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl); + g_return_if_fail(prpl_info != NULL); + + flags = gaim_conv_chat_user_get_flags(chat, old_user); + cb = gaim_conv_chat_cb_new(new_user, NULL, flags); + gaim_conv_chat_set_users(chat, + g_list_prepend(gaim_conv_chat_get_users(chat), cb)); + + if (!strcmp(chat->nick, gaim_normalize(conv->account, old_user))) { + const char *alias; + + /* Note this for later. */ + is_me = TRUE; + + alias = gaim_account_get_alias(conv->account); + if (alias != NULL) + new_alias = alias; + else + { + const char *display_name = gaim_connection_get_display_name(gc); + if (display_name != NULL) + alias = display_name; + } + } else if (!(prpl_info->options & OPT_PROTO_UNIQUE_CHATNAME)) { + GaimBuddy *buddy; + if ((buddy = gaim_find_buddy(gc->account, new_user)) != NULL) + new_alias = gaim_buddy_get_contact_alias(buddy); + } + + if (ops != NULL && ops->chat_rename_user != NULL) + ops->chat_rename_user(conv, old_user, new_user, new_alias); + + cb = gaim_conv_chat_cb_find(chat, old_user); + + if (cb) { + gaim_conv_chat_set_users(chat, + g_list_remove(gaim_conv_chat_get_users(chat), cb)); + gaim_conv_chat_cb_destroy(cb); + } + + if (gaim_conv_chat_is_user_ignored(chat, old_user)) { + gaim_conv_chat_unignore(chat, old_user); + gaim_conv_chat_ignore(chat, new_user); + } + else if (gaim_conv_chat_is_user_ignored(chat, new_user)) + gaim_conv_chat_unignore(chat, new_user); + + if (is_me) + gaim_conv_chat_set_nick(chat, new_user); + + if (gaim_prefs_get_bool("/core/conversations/chat/show_nick_change") && + !gaim_conv_chat_is_user_ignored(chat, new_user)) { + + if (is_me) { + char *escaped = g_markup_escape_text(new_user, -1); + g_snprintf(tmp, sizeof(tmp), + _("You are now known as %s"), escaped); + g_free(escaped); + } else { + const char *old_alias = old_user; + const char *new_alias = new_user; + char *escaped; + char *escaped2; + + if (!(prpl_info->options & OPT_PROTO_UNIQUE_CHATNAME)) { + GaimBuddy *buddy; + + if ((buddy = gaim_find_buddy(gc->account, old_user)) != NULL) + old_alias = gaim_buddy_get_contact_alias(buddy); + if ((buddy = gaim_find_buddy(gc->account, new_user)) != NULL) + new_alias = gaim_buddy_get_contact_alias(buddy); + } + + escaped = g_markup_escape_text(old_alias, -1); + escaped2 = g_markup_escape_text(new_alias, -1); + g_snprintf(tmp, sizeof(tmp), + _("%s is now known as %s"), escaped, escaped2); + g_free(escaped); + g_free(escaped2); + } + + gaim_conversation_write(conv, NULL, tmp, GAIM_MESSAGE_SYSTEM, time(NULL)); + } +} + +void +gaim_conv_chat_remove_user(GaimConvChat *chat, const char *user, const char *reason) +{ + GList *users = g_list_append(NULL, (char *)user); + + gaim_conv_chat_remove_users(chat, users, reason); + + g_list_free(users); +} + +void +gaim_conv_chat_remove_users(GaimConvChat *chat, GList *users, const char *reason) +{ + GaimConversation *conv; + GaimConnection *gc; + GaimPluginProtocolInfo *prpl_info; + GaimConversationUiOps *ops; + GaimConvChatBuddy *cb; + GList *l; + gboolean quiet; + + g_return_if_fail(chat != NULL); + g_return_if_fail(users != NULL); + + conv = gaim_conv_chat_get_conversation(chat); + + gc = gaim_conversation_get_gc(conv); + g_return_if_fail(gc != NULL); + prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl); + g_return_if_fail(prpl_info != NULL); + + ops = gaim_conversation_get_ui_ops(conv); + + for (l = users; l != NULL; l = l->next) { + const char *user = (const char *)l->data; + quiet = GPOINTER_TO_INT(gaim_signal_emit_return_1(gaim_conversations_get_handle(), + "chat-buddy-leaving", conv, user, reason)) | + gaim_conv_chat_is_user_ignored(chat, user); + + cb = gaim_conv_chat_cb_find(chat, user); + + if (cb) { + gaim_conv_chat_set_users(chat, + g_list_remove(gaim_conv_chat_get_users(chat), cb)); + gaim_conv_chat_cb_destroy(cb); + } + + /* NOTE: Don't remove them from ignored in case they re-enter. */ + + if (!quiet) { + const char *alias = user; + char *escaped; + char *tmp; + + if (!(prpl_info->options & OPT_PROTO_UNIQUE_CHATNAME)) { + GaimBuddy *buddy; + + if ((buddy = gaim_find_buddy(gc->account, user)) != NULL) + alias = gaim_buddy_get_contact_alias(buddy); + } + + escaped = g_markup_escape_text(alias, -1); + + if (reason == NULL || !*reason) + tmp = g_strdup_printf(_("%s left the room."), escaped); + else { + char *escaped2 = g_markup_escape_text(reason, -1); + tmp = g_strdup_printf(_("%s left the room (%s)."), + escaped, escaped2); + g_free(escaped2); + } + g_free(escaped); + + gaim_conversation_write(conv, NULL, tmp, GAIM_MESSAGE_SYSTEM, time(NULL)); + g_free(tmp); + } + + gaim_signal_emit(gaim_conversations_get_handle(), "chat-buddy-left", + conv, user, reason); + } + + if (ops != NULL && ops->chat_remove_users != NULL) + ops->chat_remove_users(conv, users); +} + +void +gaim_conv_chat_clear_users(GaimConvChat *chat) +{ + GaimConversation *conv; + GaimConversationUiOps *ops; + GList *users, *names = NULL; + GList *l; + + g_return_if_fail(chat != NULL); + + conv = gaim_conv_chat_get_conversation(chat); + ops = gaim_conversation_get_ui_ops(conv); + users = gaim_conv_chat_get_users(chat); + + if (ops != NULL && ops->chat_remove_users != NULL) { + for (l = users; l; l = l->next) { + GaimConvChatBuddy *cb = l->data; + names = g_list_append(names, cb->name); + } + ops->chat_remove_users(conv, names); + g_list_free(names); + } + + for (l = users; l; l = l->next) + { + GaimConvChatBuddy *cb = l->data; + + gaim_signal_emit(gaim_conversations_get_handle(), + "chat-buddy-leaving", conv, cb->name, NULL); + gaim_signal_emit(gaim_conversations_get_handle(), + "chat-buddy-left", conv, cb->name, NULL); + + gaim_conv_chat_cb_destroy(cb); + } + + g_list_free(users); + gaim_conv_chat_set_users(chat, NULL); +} + + +gboolean +gaim_conv_chat_find_user(GaimConvChat *chat, const char *user) +{ + g_return_val_if_fail(chat != NULL, FALSE); + g_return_val_if_fail(user != NULL, FALSE); + + return (gaim_conv_chat_cb_find(chat, user) != NULL); +} + +void +gaim_conv_chat_user_set_flags(GaimConvChat *chat, const char *user, + GaimConvChatBuddyFlags flags) +{ + GaimConversation *conv; + GaimConversationUiOps *ops; + GaimConvChatBuddy *cb; + GaimConvChatBuddyFlags oldflags; + + g_return_if_fail(chat != NULL); + g_return_if_fail(user != NULL); + + cb = gaim_conv_chat_cb_find(chat, user); + + if (!cb) + return; + + if (flags == cb->flags) + return; + + oldflags = cb->flags; + cb->flags = flags; + + conv = gaim_conv_chat_get_conversation(chat); + ops = gaim_conversation_get_ui_ops(conv); + + if (ops != NULL && ops->chat_update_user != NULL) + ops->chat_update_user(conv, user); + + gaim_signal_emit(gaim_conversations_get_handle(), + "chat-buddy-flags", conv, user, oldflags, flags); +} + +GaimConvChatBuddyFlags +gaim_conv_chat_user_get_flags(GaimConvChat *chat, const char *user) +{ + GaimConvChatBuddy *cb; + + g_return_val_if_fail(chat != NULL, 0); + g_return_val_if_fail(user != NULL, 0); + + cb = gaim_conv_chat_cb_find(chat, user); + + if (!cb) + return GAIM_CBFLAGS_NONE; + + return cb->flags; +} + +void gaim_conv_chat_set_nick(GaimConvChat *chat, const char *nick) { + g_return_if_fail(chat != NULL); + + g_free(chat->nick); + chat->nick = g_strdup(gaim_normalize(chat->conv->account, nick)); +} + +const char *gaim_conv_chat_get_nick(GaimConvChat *chat) { + g_return_val_if_fail(chat != NULL, NULL); + + return chat->nick; +} + +GaimConversation * +gaim_find_chat(const GaimConnection *gc, int id) +{ + GList *l; + GaimConversation *conv; + + for (l = gaim_get_chats(); l != NULL; l = l->next) { + conv = (GaimConversation *)l->data; + + if (gaim_conv_chat_get_id(GAIM_CONV_CHAT(conv)) == id && + gaim_conversation_get_gc(conv) == gc) + return conv; + } + + return NULL; +} + +void +gaim_conv_chat_left(GaimConvChat *chat) +{ + g_return_if_fail(chat != NULL); + + chat->left = TRUE; + gaim_conversation_update(chat->conv, GAIM_CONV_UPDATE_CHATLEFT); +} + +gboolean +gaim_conv_chat_has_left(GaimConvChat *chat) +{ + g_return_val_if_fail(chat != NULL, TRUE); + + return chat->left; +} +GaimConvChatBuddy * +gaim_conv_chat_cb_new(const char *name, const char *alias, GaimConvChatBuddyFlags flags) +{ + GaimConvChatBuddy *cb; + + g_return_val_if_fail(name != NULL, NULL); + + cb = g_new0(GaimConvChatBuddy, 1); + cb->name = g_strdup(name); + cb->flags = flags; + cb->alias = g_strdup(alias); + + GAIM_DBUS_REGISTER_POINTER(cb, GaimConvChatBuddy); + return cb; +} + +GaimConvChatBuddy * +gaim_conv_chat_cb_find(GaimConvChat *chat, const char *name) +{ + GList *l; + GaimConvChatBuddy *cb = NULL; + + g_return_val_if_fail(chat != NULL, NULL); + g_return_val_if_fail(name != NULL, NULL); + + for (l = gaim_conv_chat_get_users(chat); l; l = l->next) { + cb = l->data; + if (!gaim_utf8_strcasecmp(cb->name, name)) + return cb; + } + + return NULL; +} + +void +gaim_conv_chat_cb_destroy(GaimConvChatBuddy *cb) +{ + if (cb == NULL) + return; + + g_free(cb->alias); + g_free(cb->alias_key); + g_free(cb->name); + + GAIM_DBUS_UNREGISTER_POINTER(cb); + g_free(cb); +} + +const char * +gaim_conv_chat_cb_get_name(GaimConvChatBuddy *cb) +{ + g_return_val_if_fail(cb != NULL, NULL); + + return cb->name; +} + +void * +gaim_conversations_get_handle(void) +{ + static int handle; + + return &handle; +} + +void +gaim_conversations_init(void) +{ + void *handle = gaim_conversations_get_handle(); + + /********************************************************************** + * Register preferences + **********************************************************************/ + + /* Conversations */ + gaim_prefs_add_none("/core/conversations"); + + /* Conversations -> Chat */ + gaim_prefs_add_none("/core/conversations/chat"); + gaim_prefs_add_bool("/core/conversations/chat/show_nick_change", TRUE); + + /* Conversations -> IM */ + gaim_prefs_add_none("/core/conversations/im"); + gaim_prefs_add_bool("/core/conversations/im/send_typing", TRUE); + + + /********************************************************************** + * Register signals + **********************************************************************/ + gaim_signal_register(handle, "writing-im-msg", + gaim_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER_POINTER, + gaim_value_new(GAIM_TYPE_BOOLEAN), 5, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_ACCOUNT), + gaim_value_new(GAIM_TYPE_STRING), + gaim_value_new_outgoing(GAIM_TYPE_STRING), + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_CONVERSATION), + gaim_value_new(GAIM_TYPE_UINT)); + + gaim_signal_register(handle, "wrote-im-msg", + gaim_marshal_VOID__POINTER_POINTER_POINTER_POINTER_UINT, + NULL, 5, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_ACCOUNT), + gaim_value_new(GAIM_TYPE_STRING), + gaim_value_new(GAIM_TYPE_STRING), + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_CONVERSATION), + gaim_value_new(GAIM_TYPE_UINT)); + + gaim_signal_register(handle, "sending-im-msg", + gaim_marshal_VOID__POINTER_POINTER_POINTER, + NULL, 3, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_ACCOUNT), + gaim_value_new(GAIM_TYPE_STRING), + gaim_value_new_outgoing(GAIM_TYPE_STRING)); + + gaim_signal_register(handle, "sent-im-msg", + gaim_marshal_VOID__POINTER_POINTER_POINTER, + NULL, 3, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_ACCOUNT), + gaim_value_new(GAIM_TYPE_STRING), + gaim_value_new(GAIM_TYPE_STRING)); + + gaim_signal_register(handle, "receiving-im-msg", + gaim_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER_POINTER, + gaim_value_new(GAIM_TYPE_BOOLEAN), 5, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_ACCOUNT), + gaim_value_new_outgoing(GAIM_TYPE_STRING), + gaim_value_new_outgoing(GAIM_TYPE_STRING), + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_CONVERSATION), + gaim_value_new_outgoing(GAIM_TYPE_UINT)); + + gaim_signal_register(handle, "received-im-msg", + gaim_marshal_VOID__POINTER_POINTER_POINTER_POINTER_UINT, + NULL, 5, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_ACCOUNT), + gaim_value_new(GAIM_TYPE_STRING), + gaim_value_new(GAIM_TYPE_STRING), + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_CONVERSATION), + gaim_value_new(GAIM_TYPE_UINT)); + + gaim_signal_register(handle, "writing-chat-msg", + gaim_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER_POINTER, + gaim_value_new(GAIM_TYPE_BOOLEAN), 5, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_ACCOUNT), + gaim_value_new(GAIM_TYPE_STRING), + gaim_value_new_outgoing(GAIM_TYPE_STRING), + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_CONVERSATION), + gaim_value_new(GAIM_TYPE_UINT)); + + gaim_signal_register(handle, "wrote-chat-msg", + gaim_marshal_VOID__POINTER_POINTER_POINTER_POINTER_UINT, + NULL, 5, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_ACCOUNT), + gaim_value_new(GAIM_TYPE_STRING), + gaim_value_new(GAIM_TYPE_STRING), + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_CONVERSATION), + gaim_value_new(GAIM_TYPE_UINT)); + + gaim_signal_register(handle, "sending-chat-msg", + gaim_marshal_VOID__POINTER_POINTER_UINT, NULL, 3, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_ACCOUNT), + gaim_value_new_outgoing(GAIM_TYPE_STRING), + gaim_value_new(GAIM_TYPE_UINT)); + + gaim_signal_register(handle, "sent-chat-msg", + gaim_marshal_VOID__POINTER_POINTER_UINT, NULL, 3, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_ACCOUNT), + gaim_value_new(GAIM_TYPE_STRING), + gaim_value_new(GAIM_TYPE_UINT)); + + gaim_signal_register(handle, "receiving-chat-msg", + gaim_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER_POINTER, + gaim_value_new(GAIM_TYPE_BOOLEAN), 5, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_ACCOUNT), + gaim_value_new_outgoing(GAIM_TYPE_STRING), + gaim_value_new_outgoing(GAIM_TYPE_STRING), + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_CONVERSATION), + gaim_value_new_outgoing(GAIM_TYPE_UINT)); + + gaim_signal_register(handle, "received-chat-msg", + gaim_marshal_VOID__POINTER_POINTER_POINTER_POINTER_UINT, + NULL, 5, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_ACCOUNT), + gaim_value_new(GAIM_TYPE_STRING), + gaim_value_new(GAIM_TYPE_STRING), + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_CONVERSATION), + gaim_value_new(GAIM_TYPE_UINT)); + + gaim_signal_register(handle, "conversation-created", + gaim_marshal_VOID__POINTER, NULL, 1, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_CONVERSATION)); + + gaim_signal_register(handle, "conversation-updated", + gaim_marshal_VOID__POINTER_UINT, NULL, 2, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_CONVERSATION), + gaim_value_new(GAIM_TYPE_UINT)); + + gaim_signal_register(handle, "deleting-conversation", + gaim_marshal_VOID__POINTER, NULL, 1, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_CONVERSATION)); + + gaim_signal_register(handle, "buddy-typing", + gaim_marshal_VOID__POINTER_POINTER, NULL, 2, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_ACCOUNT), + gaim_value_new(GAIM_TYPE_STRING)); + + gaim_signal_register(handle, "buddy-typed", + gaim_marshal_VOID__POINTER_POINTER, NULL, 2, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_ACCOUNT), + gaim_value_new(GAIM_TYPE_STRING)); + + gaim_signal_register(handle, "buddy-typing-stopped", + gaim_marshal_VOID__POINTER_POINTER, NULL, 2, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_ACCOUNT), + gaim_value_new(GAIM_TYPE_STRING)); + + gaim_signal_register(handle, "chat-buddy-joining", + gaim_marshal_BOOLEAN__POINTER_POINTER_UINT, + gaim_value_new(GAIM_TYPE_BOOLEAN), 3, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_CONVERSATION), + gaim_value_new(GAIM_TYPE_STRING), + gaim_value_new(GAIM_TYPE_UINT)); + + gaim_signal_register(handle, "chat-buddy-joined", + gaim_marshal_VOID__POINTER_POINTER_UINT_UINT, NULL, 4, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_CONVERSATION), + gaim_value_new(GAIM_TYPE_STRING), + gaim_value_new(GAIM_TYPE_UINT), + gaim_value_new(GAIM_TYPE_BOOLEAN)); + + gaim_signal_register(handle, "chat-buddy-flags", + gaim_marshal_VOID__POINTER_POINTER_UINT_UINT, NULL, 4, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_CONVERSATION), + gaim_value_new(GAIM_TYPE_STRING), + gaim_value_new(GAIM_TYPE_UINT), + gaim_value_new(GAIM_TYPE_UINT)); + + gaim_signal_register(handle, "chat-buddy-leaving", + gaim_marshal_BOOLEAN__POINTER_POINTER_POINTER, + gaim_value_new(GAIM_TYPE_BOOLEAN), 3, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_CONVERSATION), + gaim_value_new(GAIM_TYPE_STRING), + gaim_value_new(GAIM_TYPE_STRING)); + + gaim_signal_register(handle, "chat-buddy-left", + gaim_marshal_VOID__POINTER_POINTER_POINTER, NULL, 3, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_CONVERSATION), + gaim_value_new(GAIM_TYPE_STRING), + gaim_value_new(GAIM_TYPE_STRING)); + + gaim_signal_register(handle, "chat-inviting-user", + gaim_marshal_VOID__POINTER_POINTER_POINTER, NULL, 3, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_CONVERSATION), + gaim_value_new(GAIM_TYPE_STRING), + gaim_value_new_outgoing(GAIM_TYPE_STRING)); + + gaim_signal_register(handle, "chat-invited-user", + gaim_marshal_VOID__POINTER_POINTER_POINTER, NULL, 3, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_CONVERSATION), + gaim_value_new(GAIM_TYPE_STRING), + gaim_value_new(GAIM_TYPE_STRING)); + + gaim_signal_register(handle, "chat-invited", + gaim_marshal_INT__POINTER_POINTER_POINTER_POINTER_POINTER, + NULL, 5, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_ACCOUNT), + gaim_value_new(GAIM_TYPE_STRING), + gaim_value_new(GAIM_TYPE_STRING), + gaim_value_new(GAIM_TYPE_STRING), + gaim_value_new(GAIM_TYPE_POINTER)); + + gaim_signal_register(handle, "chat-joined", + gaim_marshal_VOID__POINTER, NULL, 1, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_CONVERSATION)); + + gaim_signal_register(handle, "chat-left", + gaim_marshal_VOID__POINTER, NULL, 1, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_CONVERSATION)); + + gaim_signal_register(handle, "chat-topic-changed", + gaim_marshal_VOID__POINTER_POINTER_POINTER, NULL, 3, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_CONVERSATION), + gaim_value_new(GAIM_TYPE_STRING), + gaim_value_new(GAIM_TYPE_STRING)); +} + +void +gaim_conversations_uninit(void) +{ + while (conversations) + gaim_conversation_destroy((GaimConversation*)conversations->data); + gaim_signals_unregister_by_instance(gaim_conversations_get_handle()); +} diff -r d10dda2777a9 -r b63ebf84c42b core/conversation.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/conversation.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,1201 @@ +/** + * @file conversation.h Conversation API + * @ingroup core + * + * 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 + * + * @see @ref conversation-signals + */ +#ifndef _GAIM_CONVERSATION_H_ +#define _GAIM_CONVERSATION_H_ + +/**************************************************************************/ +/** Data Structures */ +/**************************************************************************/ + + +typedef struct _GaimConversationUiOps GaimConversationUiOps; +typedef struct _GaimConversation GaimConversation; +typedef struct _GaimConvIm GaimConvIm; +typedef struct _GaimConvChat GaimConvChat; +typedef struct _GaimConvChatBuddy GaimConvChatBuddy; + +/** + * A type of conversation. + */ +typedef enum +{ + GAIM_CONV_TYPE_UNKNOWN = 0, /**< Unknown conversation type. */ + GAIM_CONV_TYPE_IM, /**< Instant Message. */ + GAIM_CONV_TYPE_CHAT, /**< Chat room. */ + GAIM_CONV_TYPE_MISC, /**< A misc. conversation. */ + GAIM_CONV_TYPE_ANY /**< Any type of conversation. */ + +} GaimConversationType; + +/** + * Conversation update type. + */ +typedef enum +{ + GAIM_CONV_UPDATE_ADD = 0, /**< The buddy associated with the conversation + was added. */ + GAIM_CONV_UPDATE_REMOVE, /**< The buddy associated with the conversation + was removed. */ + GAIM_CONV_UPDATE_ACCOUNT, /**< The gaim_account was changed. */ + GAIM_CONV_UPDATE_TYPING, /**< The typing state was updated. */ + GAIM_CONV_UPDATE_UNSEEN, /**< The unseen state was updated. */ + GAIM_CONV_UPDATE_LOGGING, /**< Logging for this conversation was + enabled or disabled. */ + GAIM_CONV_UPDATE_TOPIC, /**< The topic for a chat was updated. */ + /* + * XXX These need to go when we implement a more generic core/UI event + * system. + */ + GAIM_CONV_ACCOUNT_ONLINE, /**< One of the user's accounts went online. */ + GAIM_CONV_ACCOUNT_OFFLINE, /**< One of the user's accounts went offline. */ + GAIM_CONV_UPDATE_AWAY, /**< The other user went away. */ + GAIM_CONV_UPDATE_ICON, /**< The other user's buddy icon changed. */ + GAIM_CONV_UPDATE_TITLE, + GAIM_CONV_UPDATE_CHATLEFT, + + GAIM_CONV_UPDATE_FEATURES, /**< The features for a chat have changed */ + +} GaimConvUpdateType; + +/** + * The typing state of a user. + */ +typedef enum +{ + GAIM_NOT_TYPING = 0, /**< Not typing. */ + GAIM_TYPING, /**< Currently typing. */ + GAIM_TYPED /**< Stopped typing momentarily. */ + +} GaimTypingState; + +/** + * Flags applicable to a message. Most will have send, recv or system. + */ +typedef enum +{ + GAIM_MESSAGE_SEND = 0x0001, /**< Outgoing message. */ + GAIM_MESSAGE_RECV = 0x0002, /**< Incoming message. */ + GAIM_MESSAGE_SYSTEM = 0x0004, /**< System message. */ + GAIM_MESSAGE_AUTO_RESP = 0x0008, /**< Auto response. */ + GAIM_MESSAGE_ACTIVE_ONLY = 0x0010, /**< Hint to the UI that this + message should not be + shown in conversations + which are only open for + internal UI purposes + (e.g. for contact-aware + conversions). */ + GAIM_MESSAGE_NICK = 0x0020, /**< Contains your nick. */ + GAIM_MESSAGE_NO_LOG = 0x0040, /**< Do not log. */ + GAIM_MESSAGE_WHISPER = 0x0080, /**< Whispered message. */ + GAIM_MESSAGE_ERROR = 0x0200, /**< Error message. */ + GAIM_MESSAGE_DELAYED = 0x0400, /**< Delayed message. */ + GAIM_MESSAGE_RAW = 0x0800, /**< "Raw" message - don't + apply formatting */ + GAIM_MESSAGE_IMAGES = 0x1000 /**< Message contains images */ + +} GaimMessageFlags; + +/** + * Flags applicable to users in Chats. + */ +typedef enum +{ + GAIM_CBFLAGS_NONE = 0x0000, /**< No flags */ + GAIM_CBFLAGS_VOICE = 0x0001, /**< Voiced user or "Participant" */ + GAIM_CBFLAGS_HALFOP = 0x0002, /**< Half-op */ + GAIM_CBFLAGS_OP = 0x0004, /**< Channel Op or Moderator */ + GAIM_CBFLAGS_FOUNDER = 0x0008, /**< Channel Founder */ + GAIM_CBFLAGS_TYPING = 0x0010, /**< Currently typing */ + +} GaimConvChatBuddyFlags; + +#include "account.h" +#include "buddyicon.h" +#include "log.h" +#include "server.h" + +/** + * Conversation operations and events. + * + * Any UI representing a conversation must assign a filled-out + * GaimConversationUiOps structure to the GaimConversation. + */ +struct _GaimConversationUiOps +{ + void (*create_conversation)(GaimConversation *conv); + void (*destroy_conversation)(GaimConversation *conv); + void (*write_chat)(GaimConversation *conv, const char *who, + const char *message, GaimMessageFlags flags, + time_t mtime); + void (*write_im)(GaimConversation *conv, const char *who, + const char *message, GaimMessageFlags flags, + time_t mtime); + void (*write_conv)(GaimConversation *conv, const char *name, const char *alias, + const char *message, GaimMessageFlags flags, + time_t mtime); + + void (*chat_add_users)(GaimConversation *conv, GList *cbuddies, gboolean new_arrivals); + + void (*chat_rename_user)(GaimConversation *conv, const char *old_name, + const char *new_name, const char *new_alias); + void (*chat_remove_users)(GaimConversation *conv, GList *users); + void (*chat_update_user)(GaimConversation *conv, const char *user); + + void (*present)(GaimConversation *conv); + + gboolean (*has_focus)(GaimConversation *conv); + + /* Custom Smileys */ + gboolean (*custom_smiley_add)(GaimConversation *conv, const char *smile, gboolean remote); + void (*custom_smiley_write)(GaimConversation *conv, const char *smile, + const guchar *data, gsize size); + void (*custom_smiley_close)(GaimConversation *conv, const char *smile); +}; + +/** + * Data specific to Instant Messages. + */ +struct _GaimConvIm +{ + GaimConversation *conv; /**< The parent conversation. */ + + GaimTypingState typing_state; /**< The current typing state. */ + guint typing_timeout; /**< The typing timer handle. */ + time_t type_again; /**< The type again time. */ + guint send_typed_timeout; /**< The type again timer handle. */ + + GaimBuddyIcon *icon; /**< The buddy icon. */ +}; + +/** + * Data specific to Chats. + */ +struct _GaimConvChat +{ + GaimConversation *conv; /**< The parent conversation. */ + + GList *in_room; /**< The users in the room. */ + GList *ignored; /**< Ignored users. */ + char *who; /**< The person who set the topic. */ + char *topic; /**< The topic. */ + int id; /**< The chat ID. */ + char *nick; /**< Your nick in this chat. */ + + gboolean left; /**< We left the chat and kept the window open */ +}; + +/** + * Data for "Chat Buddies" + */ +struct _GaimConvChatBuddy +{ + char *name; /**< The name */ + char *alias; /**< The alias */ + char *alias_key; /**< The alias key */ + gboolean buddy; /**< ChatBuddy is on the blist */ + GaimConvChatBuddyFlags flags; /**< Flags (ops, voice etc.) */ +}; + +/** + * A core representation of a conversation between two or more people. + * + * The conversation can be an IM or a chat. + */ +struct _GaimConversation +{ + GaimConversationType type; /**< The type of conversation. */ + + GaimAccount *account; /**< The user using this conversation. */ + + + char *name; /**< The name of the conversation. */ + char *title; /**< The window title. */ + + gboolean logging; /**< The status of logging. */ + + GList *logs; /**< This conversation's logs */ + + union + { + GaimConvIm *im; /**< IM-specific data. */ + GaimConvChat *chat; /**< Chat-specific data. */ + void *misc; /**< Misc. data. */ + + } u; + + GaimConversationUiOps *ui_ops; /**< UI-specific operations. */ + void *ui_data; /**< UI-specific data. */ + + GHashTable *data; /**< Plugin-specific data. */ + + GaimConnectionFlags features; /**< The supported features */ + +}; + +#ifdef __cplusplus +extern "C" { +#endif + +/**************************************************************************/ +/** @name Conversation API */ +/**************************************************************************/ +/*@{*/ + +/** + * Creates a new conversation of the specified type. + * + * @param type The type of conversation. + * @param account The account opening the conversation window on the gaim + * user's end. + * @param name The name of the conversation. + * + * @return The new conversation. + */ +GaimConversation *gaim_conversation_new(GaimConversationType type, + GaimAccount *account, + const char *name); + +/** + * Destroys the specified conversation and removes it from the parent + * window. + * + * If this conversation is the only one contained in the parent window, + * that window is also destroyed. + * + * @param conv The conversation to destroy. + */ +void gaim_conversation_destroy(GaimConversation *conv); + + +/** + * Present a conversation to the user. This allows core code to initiate a + * conversation by displaying the IM dialog. + * @param conv The conversation to present + */ +void gaim_conversation_present(GaimConversation *conv); + + +/** + * Returns the specified conversation's type. + * + * @param conv The conversation. + * + * @return The conversation's type. + */ +GaimConversationType gaim_conversation_get_type(const GaimConversation *conv); + +/** + * Sets the specified conversation's UI operations structure. + * + * @param conv The conversation. + * @param ops The UI conversation operations structure. + */ +void gaim_conversation_set_ui_ops(GaimConversation *conv, + GaimConversationUiOps *ops); + +/** + * Sets the default conversation UI operations structure. + * + * @param ops The UI conversation operations structure. + */ +void gaim_conversations_set_ui_ops(GaimConversationUiOps *ops); + +/** + * Returns the specified conversation's UI operations structure. + * + * @param conv The conversation. + * + * @return The operations structure. + */ +GaimConversationUiOps *gaim_conversation_get_ui_ops( + const GaimConversation *conv); + +/** + * Sets the specified conversation's gaim_account. + * + * This gaim_account represents the user using gaim, not the person the user + * is having a conversation/chat/flame with. + * + * @param conv The conversation. + * @param account The gaim_account. + */ +void gaim_conversation_set_account(GaimConversation *conv, + GaimAccount *account); + +/** + * Returns the specified conversation's gaim_account. + * + * This gaim_account represents the user using gaim, not the person the user + * is having a conversation/chat/flame with. + * + * @param conv The conversation. + * + * @return The conversation's gaim_account. + */ +GaimAccount *gaim_conversation_get_account(const GaimConversation *conv); + +/** + * Returns the specified conversation's gaim_connection. + * + * This is the same as gaim_conversation_get_user(conv)->gc. + * + * @param conv The conversation. + * + * @return The conversation's gaim_connection. + */ +GaimConnection *gaim_conversation_get_gc(const GaimConversation *conv); + +/** + * Sets the specified conversation's title. + * + * @param conv The conversation. + * @param title The title. + */ +void gaim_conversation_set_title(GaimConversation *conv, const char *title); + +/** + * Returns the specified conversation's title. + * + * @param conv The conversation. + * + * @return The title. + */ +const char *gaim_conversation_get_title(const GaimConversation *conv); + +/** + * Automatically sets the specified conversation's title. + * + * This function takes OPT_IM_ALIAS_TAB into account, as well as the + * user's alias. + * + * @param conv The conversation. + */ +void gaim_conversation_autoset_title(GaimConversation *conv); + +/** + * Sets the specified conversation's name. + * + * @param conv The conversation. + * @param name The conversation's name. + */ +void gaim_conversation_set_name(GaimConversation *conv, const char *name); + +/** + * Returns the specified conversation's name. + * + * @param conv The conversation. + * + * @return The conversation's name. + */ +const char *gaim_conversation_get_name(const GaimConversation *conv); + +/** + * Enables or disables logging for this conversation. + * + * @param conv The conversation. + * @param log @c TRUE if logging should be enabled, or @c FALSE otherwise. + */ +void gaim_conversation_set_logging(GaimConversation *conv, gboolean log); + +/** + * Returns whether or not logging is enabled for this conversation. + * + * @param conv The conversation. + * + * @return @c TRUE if logging is enabled, or @c FALSE otherwise. + */ +gboolean gaim_conversation_is_logging(const GaimConversation *conv); + +/** + * Closes any open logs for this conversation. + * + * Note that new logs will be opened as necessary (e.g. upon receipt of a + * message, if the conversation has logging enabled. To disable logging for + * the remainder of the conversation, use gaim_conversation_set_logging(). + * + * @param conv The conversation. + */ +void gaim_conversation_close_logs(GaimConversation *conv); + +/** + * Returns the specified conversation's IM-specific data. + * + * If the conversation type is not GAIM_CONV_TYPE_IM, this will return @c NULL. + * + * @param conv The conversation. + * + * @return The IM-specific data. + */ +GaimConvIm *gaim_conversation_get_im_data(const GaimConversation *conv); + +#define GAIM_CONV_IM(c) (gaim_conversation_get_im_data(c)) + +/** + * Returns the specified conversation's chat-specific data. + * + * If the conversation type is not GAIM_CONV_TYPE_CHAT, this will return @c NULL. + * + * @param conv The conversation. + * + * @return The chat-specific data. + */ +GaimConvChat *gaim_conversation_get_chat_data(const GaimConversation *conv); + +#define GAIM_CONV_CHAT(c) (gaim_conversation_get_chat_data(c)) + +/** + * Sets extra data for a conversation. + * + * @param conv The conversation. + * @param key The unique key. + * @param data The data to assign. + */ +void gaim_conversation_set_data(GaimConversation *conv, const char *key, + gpointer data); + +/** + * Returns extra data in a conversation. + * + * @param conv The conversation. + * @param key The unqiue key. + * + * @return The data associated with the key. + */ +gpointer gaim_conversation_get_data(GaimConversation *conv, const char *key); + +/** + * Returns a list of all conversations. + * + * This list includes both IMs and chats. + * + * @return A GList of all conversations. + */ +GList *gaim_get_conversations(void); + +/** + * Returns a list of all IMs. + * + * @return A GList of all IMs. + */ +GList *gaim_get_ims(void); + +/** + * Returns a list of all chats. + * + * @return A GList of all chats. + */ +GList *gaim_get_chats(void); + +/** + * Finds a conversation with the specified type, name, and Gaim account. + * + * @param type The type of the conversation. + * @param name The name of the conversation. + * @param account The gaim_account associated with the conversation. + * + * @return The conversation if found, or @c NULL otherwise. + */ +GaimConversation *gaim_find_conversation_with_account( + GaimConversationType type, const char *name, + const GaimAccount *account); + +/** + * Writes to a conversation window. + * + * This function should not be used to write IM or chat messages. Use + * gaim_conv_im_write() and gaim_conv_chat_write() instead. Those functions will + * most likely call this anyway, but they may do their own formatting, + * sound playback, etc. + * + * This can be used to write generic messages, such as "so and so closed + * the conversation window." + * + * @param conv The conversation. + * @param who The user who sent the message. + * @param message The message. + * @param flags The message flags. + * @param mtime The time the message was sent. + * + * @see gaim_conv_im_write() + * @see gaim_conv_chat_write() + */ +void gaim_conversation_write(GaimConversation *conv, const char *who, + const char *message, GaimMessageFlags flags, + time_t mtime); + + +/** + Set the features as supported for the given conversation. + @param conv The conversation + @param features Bitset defining supported features +*/ +void gaim_conversation_set_features(GaimConversation *conv, + GaimConnectionFlags features); + + +/** + Get the features supported by the given conversation. + @param conv The conversation +*/ +GaimConnectionFlags gaim_conversation_get_features(GaimConversation *conv); + +/** + * Determines if a conversation has focus + * + * @param conv The conversation. + * + * @return @c TRUE if the conversation has focus, @c FALSE if + * it does not or the UI does not have a concept of conversation focus + */ +gboolean gaim_conversation_has_focus(GaimConversation *conv); + +/** + * Updates the visual status and UI of a conversation. + * + * @param conv The conversation. + * @param type The update type. + */ +void gaim_conversation_update(GaimConversation *conv, GaimConvUpdateType type); + +/** + * Calls a function on each conversation. + * + * @param func The function. + */ +void gaim_conversation_foreach(void (*func)(GaimConversation *conv)); + +/*@}*/ + + +/**************************************************************************/ +/** @name IM Conversation API */ +/**************************************************************************/ +/*@{*/ + +/** + * Gets an IM's parent conversation. + * + * @param im The IM. + * + * @return The parent conversation. + */ +GaimConversation *gaim_conv_im_get_conversation(const GaimConvIm *im); + +/** + * Sets the IM's buddy icon. + * + * This should only be called from within Gaim. You probably want to + * call gaim_buddy_icon_set_data(). + * + * @param im The IM. + * @param icon The buddy icon. + * + * @see gaim_buddy_icon_set_data() + */ +void gaim_conv_im_set_icon(GaimConvIm *im, GaimBuddyIcon *icon); + +/** + * Returns the IM's buddy icon. + * + * @param im The IM. + * + * @return The buddy icon. + */ +GaimBuddyIcon *gaim_conv_im_get_icon(const GaimConvIm *im); + +/** + * Sets the IM's typing state. + * + * @param im The IM. + * @param state The typing state. + */ +void gaim_conv_im_set_typing_state(GaimConvIm *im, GaimTypingState state); + +/** + * Returns the IM's typing state. + * + * @param im The IM. + * + * @return The IM's typing state. + */ +GaimTypingState gaim_conv_im_get_typing_state(const GaimConvIm *im); + +/** + * Starts the IM's typing timeout. + * + * @param im The IM. + * @param timeout The timeout. + */ +void gaim_conv_im_start_typing_timeout(GaimConvIm *im, int timeout); + +/** + * Stops the IM's typing timeout. + * + * @param im The IM. + */ +void gaim_conv_im_stop_typing_timeout(GaimConvIm *im); + +/** + * Returns the IM's typing timeout. + * + * @param im The IM. + * + * @return The timeout. + */ +guint gaim_conv_im_get_typing_timeout(const GaimConvIm *im); + +/** + * Sets the quiet-time when no GAIM_TYPING messages will be sent. + * Few protocols need this (maybe only MSN). If the user is still + * typing after this quiet-period, then another GAIM_TYPING message + * will be sent. + * + * @param im The IM. + * @param val The number of seconds to wait before allowing another + * GAIM_TYPING message to be sent to the user. Or 0 to + * not send another GAIM_TYPING message. + */ +void gaim_conv_im_set_type_again(GaimConvIm *im, unsigned int val); + +/** + * Returns the time after which another GAIM_TYPING message should be sent. + * + * @param im The IM. + * + * @return The time in seconds since the epoch. Or 0 if no additional + * GAIM_TYPING message should be sent. + */ +time_t gaim_conv_im_get_type_again(const GaimConvIm *im); + +/** + * Starts the IM's type again timeout. + * + * @param im The IM. + */ +void gaim_conv_im_start_send_typed_timeout(GaimConvIm *im); + +/** + * Stops the IM's type again timeout. + * + * @param im The IM. + */ +void gaim_conv_im_stop_send_typed_timeout(GaimConvIm *im); + +/** + * Returns the IM's type again timeout interval. + * + * @param im The IM. + * + * @return The type again timeout interval. + */ +guint gaim_conv_im_get_send_typed_timeout(const GaimConvIm *im); + +/** + * Updates the visual typing notification for an IM conversation. + * + * @param im The IM. + */ +void gaim_conv_im_update_typing(GaimConvIm *im); + +/** + * Writes to an IM. + * + * @param im The IM. + * @param who The user who sent the message. + * @param message The message to write. + * @param flags The message flags. + * @param mtime The time the message was sent. + */ +void gaim_conv_im_write(GaimConvIm *im, const char *who, + const char *message, GaimMessageFlags flags, + time_t mtime); + +/** + * Presents an IM-error to the user + * + * This is a helper function to find a conversation, write an error to it, and + * raise the window. If a conversation with this user doesn't already exist, + * the function will return FALSE and the calling function can attempt to present + * the error another way (gaim_notify_error, most likely) + * + * @param who The user this error is about + * @param account The account this error is on + * @param what The error + * @return TRUE if the error was presented, else FALSE + */ +gboolean gaim_conv_present_error(const char *who, GaimAccount *account, const char *what); + +/** + * Sends a message to this IM conversation. + * + * @param im The IM. + * @param message The message to send. + */ +void gaim_conv_im_send(GaimConvIm *im, const char *message); + +/** + * Sends a message to this IM conversation with specified flags. + * + * @param im The IM. + * @param message The message to send. + * @param flags The GaimMessageFlags flags to use in addition to GAIM_MESSAGE_SEND. + */ +void gaim_conv_im_send_with_flags(GaimConvIm *im, const char *message, GaimMessageFlags flags); + +/** + * Adds a smiley to the conversation's smiley tree. If this returns + * @c TRUE you should call gaim_conv_custom_smiley_write() one or more + * times, and then gaim_conv_custom_smiley_close(). If this returns + * @c FALSE, either the conv or smile were invalid, or the icon was + * found in the cache. In either case, calling write or close would + * be an error. + * + * @param conv The conversation to associate the smiley with. + * @param smile The text associated with the smiley + * @param cksum_type The type of checksum. + * @param chksum The checksum, as a NUL terminated base64 string. + * @param remote @c TRUE if the custom smiley is set by the remote user (buddy). + * @return @c TRUE if an icon is expected, else FALSE. Note that + * it is an error to never call gaim_conv_custom_smiley_close if + * this function returns @c TRUE, but an error to call it if + * @c FALSE is returned. + */ + +gboolean gaim_conv_custom_smiley_add(GaimConversation *conv, const char *smile, + const char *cksum_type, const char *chksum, + gboolean remote); + + +/** + * Updates the image associated with the current smiley. + * + * @param conv The conversation associated with the smiley. + * @param smile The text associated with the smiley. + * @param data The actual image data. + * @param size The length of the data. + */ + +void gaim_conv_custom_smiley_write(GaimConversation *conv, + const char *smile, + const guchar *data, + gsize size); + +/** + * Close the custom smiley, all data has been written with + * gaim_conv_custom_smiley_write, and it is no longer valid + * to call that function on that smiley. + * + * @param conv The gaim conversation associated with the smiley. + * @param smile The text associated with the smiley + */ + +void gaim_conv_custom_smiley_close(GaimConversation *conv, const char *smile); + +/*@}*/ + + +/**************************************************************************/ +/** @name Chat Conversation API */ +/**************************************************************************/ +/*@{*/ + +/** + * Gets a chat's parent conversation. + * + * @param chat The chat. + * + * @return The parent conversation. + */ +GaimConversation *gaim_conv_chat_get_conversation(const GaimConvChat *chat); + +/** + * Sets the list of users in the chat room. + * + * @note Calling this function will not update the display of the users. + * Please use gaim_conv_chat_add_user(), gaim_conv_chat_add_users(), + * gaim_conv_chat_remove_user(), and gaim_conv_chat_remove_users() instead. + * + * @param chat The chat. + * @param users The list of users. + * + * @return The list passed. + */ +GList *gaim_conv_chat_set_users(GaimConvChat *chat, GList *users); + +/** + * Returns a list of users in the chat room. + * + * @param chat The chat. + * + * @return The list of users. + */ +GList *gaim_conv_chat_get_users(const GaimConvChat *chat); + +/** + * Ignores a user in a chat room. + * + * @param chat The chat. + * @param name The name of the user. + */ +void gaim_conv_chat_ignore(GaimConvChat *chat, const char *name); + +/** + * Unignores a user in a chat room. + * + * @param chat The chat. + * @param name The name of the user. + */ +void gaim_conv_chat_unignore(GaimConvChat *chat, const char *name); + +/** + * Sets the list of ignored users in the chat room. + * + * @param chat The chat. + * @param ignored The list of ignored users. + * + * @return The list passed. + */ +GList *gaim_conv_chat_set_ignored(GaimConvChat *chat, GList *ignored); + +/** + * Returns the list of ignored users in the chat room. + * + * @param chat The chat. + * + * @return The list of ignored users. + */ +GList *gaim_conv_chat_get_ignored(const GaimConvChat *chat); + +/** + * Returns the actual name of the specified ignored user, if it exists in + * the ignore list. + * + * If the user found contains a prefix, such as '+' or '\@', this is also + * returned. The username passed to the function does not have to have this + * formatting. + * + * @param chat The chat. + * @param user The user to check in the ignore list. + * + * @return The ignored user if found, complete with prefixes, or @c NULL + * if not found. + */ +const char *gaim_conv_chat_get_ignored_user(const GaimConvChat *chat, + const char *user); + +/** + * Returns @c TRUE if the specified user is ignored. + * + * @param chat The chat. + * @param user The user. + * + * @return @c TRUE if the user is in the ignore list; @c FALSE otherwise. + */ +gboolean gaim_conv_chat_is_user_ignored(const GaimConvChat *chat, + const char *user); + +/** + * Sets the chat room's topic. + * + * @param chat The chat. + * @param who The user that set the topic. + * @param topic The topic. + */ +void gaim_conv_chat_set_topic(GaimConvChat *chat, const char *who, + const char *topic); + +/** + * Returns the chat room's topic. + * + * @param chat The chat. + * + * @return The chat's topic. + */ +const char *gaim_conv_chat_get_topic(const GaimConvChat *chat); + +/** + * Sets the chat room's ID. + * + * @param chat The chat. + * @param id The ID. + */ +void gaim_conv_chat_set_id(GaimConvChat *chat, int id); + +/** + * Returns the chat room's ID. + * + * @param chat The chat. + * + * @return The ID. + */ +int gaim_conv_chat_get_id(const GaimConvChat *chat); + +/** + * Writes to a chat. + * + * @param chat The chat. + * @param who The user who sent the message. + * @param message The message to write. + * @param flags The flags. + * @param mtime The time the message was sent. + */ +void gaim_conv_chat_write(GaimConvChat *chat, const char *who, + const char *message, GaimMessageFlags flags, + time_t mtime); + +/** + * Sends a message to this chat conversation. + * + * @param chat The chat. + * @param message The message to send. + */ +void gaim_conv_chat_send(GaimConvChat *chat, const char *message); + +/** + * Sends a message to this chat conversation with specified flags. + * + * @param chat The chat. + * @param message The message to send. + * @param flags The GaimMessageFlags flags to use. + */ +void gaim_conv_chat_send_with_flags(GaimConvChat *chat, const char *message, GaimMessageFlags flags); + +/** + * Adds a user to a chat. + * + * @param chat The chat. + * @param user The user to add. + * @param extra_msg An extra message to display with the join message. + * @param flags The users flags + * @param new_arrival Decides whether or not to show a join notice. + */ +void gaim_conv_chat_add_user(GaimConvChat *chat, const char *user, + const char *extra_msg, GaimConvChatBuddyFlags flags, + gboolean new_arrival); + +/** + * Adds a list of users to a chat. + * + * The data is copied from @a users, @a extra_msgs, and @a flags, so it is up to + * the caller to free this list after calling this function. + * + * @param chat The chat. + * @param users The list of users to add. + * @param extra_msgs An extra message to display with the join message for each + * user. This list may be shorter than @a users, in which + * case, the users after the end of extra_msgs will not have + * an extra message. By extension, this means that extra_msgs + * can simply be @c NULL and none of the users will have an + * extra message. + * @param flags The list of flags for each user. + * @param new_arrivals Decides whether or not to show join notices. + */ +void gaim_conv_chat_add_users(GaimConvChat *chat, GList *users, GList *extra_msgs, + GList *flags, gboolean new_arrivals); + +/** + * Renames a user in a chat. + * + * @param chat The chat. + * @param old_user The old username. + * @param new_user The new username. + */ +void gaim_conv_chat_rename_user(GaimConvChat *chat, const char *old_user, + const char *new_user); + +/** + * Removes a user from a chat, optionally with a reason. + * + * It is up to the developer to free this list after calling this function. + * + * @param chat The chat. + * @param user The user that is being removed. + * @param reason The optional reason given for the removal. Can be @c NULL. + */ +void gaim_conv_chat_remove_user(GaimConvChat *chat, const char *user, + const char *reason); + +/** + * Removes a list of users from a chat, optionally with a single reason. + * + * @param chat The chat. + * @param users The users that are being removed. + * @param reason The optional reason given for the removal. Can be @c NULL. + */ +void gaim_conv_chat_remove_users(GaimConvChat *chat, GList *users, + const char *reason); + +/** + * Finds a user in a chat + * + * @param chat The chat. + * @param user The user to look for. + * + * @return TRUE if the user is in the chat, FALSE if not + */ +gboolean gaim_conv_chat_find_user(GaimConvChat *chat, const char *user); + +/** + * Set a users flags in a chat + * + * @param chat The chat. + * @param user The user to update. + * @param flags The new flags. + */ +void gaim_conv_chat_user_set_flags(GaimConvChat *chat, const char *user, + GaimConvChatBuddyFlags flags); + +/** + * Get the flags for a user in a chat + * + * @param chat The chat. + * @param user The user to find the flags for + * + * @return The flags for the user + */ +GaimConvChatBuddyFlags gaim_conv_chat_user_get_flags(GaimConvChat *chat, + const char *user); + +/** + * Clears all users from a chat. + * + * @param chat The chat. + */ +void gaim_conv_chat_clear_users(GaimConvChat *chat); + +/** + * Sets your nickname (used for hilighting) for a chat. + * + * @param chat The chat. + * @param nick The nick. + */ +void gaim_conv_chat_set_nick(GaimConvChat *chat, const char *nick); + +/** + * Gets your nickname (used for hilighting) for a chat. + * + * @param chat The chat. + * @return The nick. + */ +const char *gaim_conv_chat_get_nick(GaimConvChat *chat); + +/** + * Finds a chat with the specified chat ID. + * + * @param gc The gaim_connection. + * @param id The chat ID. + * + * @return The chat conversation. + */ +GaimConversation *gaim_find_chat(const GaimConnection *gc, int id); + +/** + * Lets the core know we left a chat, without destroying it. + * Called from serv_got_chat_left(). + * + * @param chat The chat. + */ +void gaim_conv_chat_left(GaimConvChat *chat); + +/** + * Returns true if we're no longer in this chat, + * and just left the window open. + * + * @param chat The chat. + * + * @return @c TRUE if we left the chat already, @c FALSE if + * we're still there. + */ +gboolean gaim_conv_chat_has_left(GaimConvChat *chat); + +/** + * Creates a new chat buddy + * + * @param name The name. + * @param alias The alias. + * @param flags The flags. + * + * @return The new chat buddy + */ +GaimConvChatBuddy *gaim_conv_chat_cb_new(const char *name, const char *alias, + GaimConvChatBuddyFlags flags); + +/** + * Find a chat buddy in a chat + * + * @param chat The chat. + * @param name The name of the chat buddy to find. + */ +GaimConvChatBuddy *gaim_conv_chat_cb_find(GaimConvChat *chat, const char *name); + +/** + * Get the name of a chat buddy + * + * @param cb The chat buddy. + * + * @return The name of the chat buddy. + */ +const char *gaim_conv_chat_cb_get_name(GaimConvChatBuddy *cb); + +/** + * Destroys a chat buddy + * + * @param cb The chat buddy to destroy + */ +void gaim_conv_chat_cb_destroy(GaimConvChatBuddy *cb); + +/*@}*/ + +/**************************************************************************/ +/** @name Conversations Subsystem */ +/**************************************************************************/ +/*@{*/ + +/** + * Returns the conversation subsystem handle. + * + * @return The conversation subsystem handle. + */ +void *gaim_conversations_get_handle(void); + +/** + * Initializes the conversation subsystem. + */ +void gaim_conversations_init(void); + +/** + * Uninitializes the conversation subsystem. + */ +void gaim_conversations_uninit(void); + +/*@}*/ + +#ifdef __cplusplus +} +#endif + +#endif /* _GAIM_CONVERSATION_H_ */ diff -r d10dda2777a9 -r b63ebf84c42b core/core.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/core.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,245 @@ +/** + * @file core.c Gaim Core API + * @ingroup core + * + * 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 "cipher.h" +#include "connection.h" +#include "conversation.h" +#include "core.h" +#include "debug.h" +#include "ft.h" +#include "idle.h" +#include "network.h" +#include "notify.h" +#include "plugin.h" +#include "pounce.h" +#include "prefs.h" +#include "privacy.h" +#include "proxy.h" +#include "savedstatuses.h" +#include "signals.h" +#include "sslconn.h" +#include "status.h" +#include "stun.h" +#include "sound.h" + +#ifdef HAVE_DBUS +# include "dbus-server.h" +#endif + +struct GaimCore +{ + char *ui; + + void *reserved; +}; + +static GaimCoreUiOps *_ops = NULL; +static GaimCore *_core = NULL; + +STATIC_PROTO_INIT + +gboolean +gaim_core_init(const char *ui) +{ + GaimCoreUiOps *ops; + GaimCore *core; + + g_return_val_if_fail(ui != NULL, FALSE); + g_return_val_if_fail(gaim_get_core() == NULL, FALSE); + + _core = core = g_new0(GaimCore, 1); + core->ui = g_strdup(ui); + core->reserved = NULL; + + ops = gaim_core_get_ui_ops(); + + /* The signals subsystem is important and should be first. */ + gaim_signals_init(); + + gaim_signal_register(core, "quitting", gaim_marshal_VOID, NULL, 0); + + /* The prefs subsystem needs to be initialized before static protocols + * for protocol prefs to work. */ + gaim_prefs_init(); + + gaim_debug_init(); + + if (ops != NULL) + { + if (ops->ui_prefs_init != NULL) + ops->ui_prefs_init(); + + if (ops->debug_ui_init != NULL) + ops->debug_ui_init(); + } + +#ifdef HAVE_DBUS + gaim_dbus_init(); +#endif + + /* Initialize all static protocols. */ + static_proto_init(); + + /* Since plugins get probed so early we should probably initialize their + * subsystem right away too. + */ + gaim_plugins_init(); + gaim_plugins_probe(G_MODULE_SUFFIX); + + /* Accounts use status and buddy icons, so initialize these before accounts */ + gaim_status_init(); + gaim_buddy_icons_init(); + + gaim_accounts_init(); + gaim_savedstatuses_init(); + gaim_ciphers_init(); + gaim_notify_init(); + gaim_connections_init(); + gaim_conversations_init(); + gaim_blist_init(); + gaim_log_init(); + gaim_network_init(); + gaim_privacy_init(); + gaim_pounces_init(); + gaim_proxy_init(); + gaim_sound_init(); + gaim_ssl_init(); + gaim_stun_init(); + gaim_xfers_init(); + gaim_idle_init(); + + /* Call this early on to try to auto-detect our IP address */ + gaim_network_get_my_ip(-1); + + if (ops != NULL && ops->ui_init != NULL) + ops->ui_init(); + + return TRUE; +} + +void +gaim_core_quit(void) +{ + GaimCoreUiOps *ops; + GaimCore *core = gaim_get_core(); + + g_return_if_fail(core != NULL); + + /* The self destruct sequence has been initiated */ + gaim_signal_emit(gaim_get_core(), "quitting"); + + /* Transmission ends */ + gaim_connections_disconnect_all(); + + /* Save .xml files, remove signals, etc. */ + gaim_idle_uninit(); + gaim_ssl_uninit(); + gaim_pounces_uninit(); + gaim_blist_uninit(); + gaim_ciphers_uninit(); + gaim_notify_uninit(); + gaim_conversations_uninit(); + gaim_connections_uninit(); + gaim_buddy_icons_uninit(); + gaim_accounts_uninit(); + gaim_savedstatuses_uninit(); + gaim_status_uninit(); + gaim_prefs_uninit(); + gaim_xfers_uninit(); + + gaim_debug_info("main", "Unloading all plugins\n"); + gaim_plugins_destroy_all(); + + ops = gaim_core_get_ui_ops(); + if (ops != NULL && ops->quit != NULL) + ops->quit(); + + /* + * gaim_sound_uninit() should be called as close to + * shutdown as possible. This is because the call + * to ao_shutdown() can sometimes leave our + * environment variables in an unusable state, which + * can cause a crash when getenv is called (by gettext + * for example). See the complete bug report at + * http://trac.xiph.org/cgi-bin/trac.cgi/ticket/701 + * + * TODO: Eventually move this call higher up with the others. + */ + gaim_sound_uninit(); + + gaim_plugins_uninit(); + gaim_signals_uninit(); + +#ifdef HAVE_DBUS + gaim_dbus_uninit(); +#endif + + g_free(core->ui); + g_free(core); + + _core = NULL; +} + +gboolean +gaim_core_quit_cb(gpointer unused) +{ + gaim_core_quit(); + + return FALSE; +} + +const char * +gaim_core_get_version(void) +{ + return VERSION; +} + +const char * +gaim_core_get_ui(void) +{ + GaimCore *core = gaim_get_core(); + + g_return_val_if_fail(core != NULL, NULL); + + return core->ui; +} + +GaimCore * +gaim_get_core(void) +{ + return _core; +} + +void +gaim_core_set_ui_ops(GaimCoreUiOps *ops) +{ + _ops = ops; +} + +GaimCoreUiOps * +gaim_core_get_ui_ops(void) +{ + return _ops; +} diff -r d10dda2777a9 -r b63ebf84c42b core/core.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/core.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,145 @@ +/** + * @defgroup core Gaim Core + * + * 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 + */ +#ifndef _GAIM_CORE_H_ +#define _GAIM_CORE_H_ + +typedef struct GaimCore GaimCore; + +typedef struct +{ + void (*ui_prefs_init)(void); + void (*debug_ui_init)(void); /* Unfortunate necessity. */ + void (*ui_init)(void); + void (*quit)(void); + +} GaimCoreUiOps; + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Initializes the core of gaim. + * + * This will setup preferences for all the core subsystems. + * + * @param ui The ID of the UI using the core. This should be a + * unique ID, registered with the gaim team. + * + * @return @c TRUE if successful, or @c FALSE otherwise. + */ +gboolean gaim_core_init(const char *ui); + +/** + * Quits the core of gaim, which, depending on the UI, may quit the + * application using the gaim core. + */ +void gaim_core_quit(void); + +/** + * Calls gaim_core_quit(). This can be used as the function + * passed to gaim_timeout_add() when you want to shutdown Gaim + * in a specified amount of time. When shutting down Gaim + * from a plugin, you must use this with a timeout value of 0: + * gaim_timeout_add(0, gaim_core_quitcb, NULL); + * This is ensures that code from your plugin is not being + * executed when gaim_core_quit() is called. Otherwise you + * would get a core dump after gaim_core_quit() executes and + * control returns to your plugin because gaim_core_quit() frees + * all plugins. + */ +gboolean gaim_core_quit_cb(gpointer unused); + +/** + * Returns the version of the core library. + * + * @return The version of the core library. + */ +const char *gaim_core_get_version(void); + +/** + * Returns the ID of the UI that is using the core. + * + * @return The ID of the UI that is currently using the core. + */ +const char *gaim_core_get_ui(void); + +/** + * Returns a handle to the gaim core. + * + * This is used for such things as signals. + */ +GaimCore *gaim_get_core(void); + +/** + * Sets the UI ops for the core. + * + * @param ops A UI ops structure for the core. + */ +void gaim_core_set_ui_ops(GaimCoreUiOps *ops); + +/** + * Returns the UI ops for the core. + * + * @return The core's UI ops structure. + */ +GaimCoreUiOps *gaim_core_get_ui_ops(void); + +#ifdef __cplusplus +} +#endif + +#endif /* _GAIM_CORE_H_ */ + +/* + + /===- + `//"\\ """"`---.___.-"" + ______-==| | | \\ _-"` + __--""" ,-/-==\\ | | `\ ,' + _-" /' | \\ ___ / / \ / + .' / | \\ /" "\ /' / \ /' + / ____ / | \`\.__/-"" D O \_/' / \/' +/-'" """""---__ | "-/" O G R /' _--"` + \_| / R __--_ t ), __--"" + '""--_/ T _-"_>--<_\ h '-" \ + {\__--_/} / \\__>--<__\ e B \ + /' (_/ _-" | |__>--<__| U | + | _/) )-" | |__>--<__| R | + / /" ,_/ / /__>---<__/ N | + o-o _// /-"_>---<__-" I / + (^(" /"_>---<__- N _-" + ,/| /__>--<__/ A _-" + ,//('( |__>--<__| T / .----_ + ( ( ')) |__>--<__| | /' _---_"\ + `-)) )) ( |__>--<__| O | /' / "\`\ + ,/,'//( ( \__>--<__\ R \ /' // || + ,( ( ((, )) "-__>--<_"-_ "--____---"' _/'/ /' + `"/ )` ) ,/| "-_">--<_/-__ __-" _/ + ._-"//( )/ )) ` ""-'_/_/ /"""""""__--" + ;'( ')/ ,)( """""""""" + ' ') '( (/ + ' ' ` + +*/ diff -r d10dda2777a9 -r b63ebf84c42b core/dbus-analyze-functions.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/dbus-analyze-functions.py Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,543 @@ +import re +import string +import sys + + +# types translated into "int" +simpletypes = ["int", "gint", "guint", "gboolean"] + +# List "excluded" contains functions that shouldn't be exported via +# DBus. If you remove a function from this list, please make sure +# that it does not break "make" with the configure option +# "--enable-dbus" turned on. + +excluded = [\ + # I don't remember why this function is excluded; something to do + # with the fact that it takes a (const) GList as a parameter. + "gaim_presence_add_list", + + # These functions are excluded because they involve value of the + # type GaimConvPlacementFunc, which is a pointer to a function and + # (currently?) can't be translated into a DBus type. Normally, + # functions with untranslatable types are skipped, but this script + # assumes that all non-pointer type names beginning with "Gaim" + # are enums, which is not true in this case. + "gaim_conv_placement_add_fnc", + "gaim_conv_placement_get_fnc", + "gaim_conv_placement_get_current_func", + "gaim_conv_placement_set_current_func", + + # This is excluded because this script treats GaimLogReadFlags* + # as pointer to a struct, instead of a pointer to an enum. This + # causes a compilation error. Someone should fix this script. + "gaim_log_read", + ] + +# This is a list of functions that return a GList* whose elements are +# string, not pointers to objects. Don't put any functions here, it +# won't work. +stringlists = [] + +pointer = "#pointer#" +myexception = "My Exception" + +def ctopascal(name): + newname = "" + for word in name.split("_"): + newname += word.capitalize() + return newname + +class Parameter: + def __init__(self, type, name): + self.name = name + self.type = type + + def fromtokens(tokens, parameternumber = -1): + if len(tokens) == 0: + raise myexception + if (len(tokens) == 1) or (tokens[-1] == pointer): + if parameternumber >= 0: + return Parameter(tokens, "param%i" % parameternumber) + else: + raise myexception + else: + return Parameter(tokens[:-1], tokens[-1]) + + fromtokens = staticmethod(fromtokens) + +class Binding: + def __init__(self, functiontext, paramtexts): + self.function = Parameter.fromtokens(functiontext.split()) + + if self.function.name in excluded: + raise myexception + + self.params = [] + for i in range(len(paramtexts)): + self.params.append(Parameter.fromtokens(paramtexts[i].split(), i)) + + self.call = "%s(%s)" % (self.function.name, + ", ".join([param.name for param in self.params])) + + + def process(self): + for param in self.params: + self.processinput(param.type, param.name) + + self.processoutput(self.function.type, "RESULT") + self.flush() + + + def processinput(self, type, name): + const = False + if type[0] == "const": + type = type[1:] + const = True + + if len(type) == 1: + # simple types (int, gboolean, etc.) and enums + if (type[0] in simpletypes) or (type[0].startswith("Gaim")): + return self.inputsimple(type, name) + + + # va_list, replace by NULL + if type[0] == "va_list": + return self.inputvalist(type, name) + + # pointers ... + if (len(type) == 2) and (type[1] == pointer): + # strings + if type[0] == "char": + if const: + return self.inputstring(type, name) + else: + raise myexception + + elif type[0] == "GHashTable": + return self.inputhash(type, name) + + # known object types are transformed to integer handles + elif type[0].startswith("Gaim"): + return self.inputgaimstructure(type, name) + + # unknown pointers are always replaced with NULL + else: + return self.inputpointer(type, name) + return + + raise myexception + + + def processoutput(self, type, name): + # the "void" type is simple ... + if type == ["void"]: + return self.outputvoid(type, name) + + const = False + if type[0] == "const": + type = type[1:] + const = True + + # a string + if type == ["char", pointer]: + return self.outputstring(type, name, const) + + # simple types (ints, booleans, enums, ...) + if (len(type) == 1) and \ + ((type[0] in simpletypes) or (type[0].startswith("Gaim"))): + return self.outputsimple(type, name) + + # pointers ... + if (len(type) == 2) and (type[1] == pointer): + + # handles + if type[0].startswith("Gaim"): + return self.outputgaimstructure(type, name) + + if type[0] in ["GList", "GSList"]: + return self.outputlist(type, name) + + raise myexception + + +class ClientBinding (Binding): + def __init__(self, functiontext, paramtexts, knowntypes, headersonly): + Binding.__init__(self, functiontext, paramtexts) + self.knowntypes = knowntypes + self.headersonly = headersonly + self.paramshdr = [] + self.decls = [] + self.inputparams = [] + self.outputparams = [] + self.returncode = [] + + def flush(self): + print "%s %s(%s)" % (self.functiontype, self.function.name, + ", ".join(self.paramshdr)), + + if self.headersonly: + print ";" + return + + print "{" + + for decl in self.decls: + print decl + + print 'dbus_g_proxy_call(gaim_proxy, "%s", NULL,' % ctopascal(self.function.name) + + for type_name in self.inputparams: + print "\t%s, %s, " % type_name, + print "G_TYPE_INVALID," + + for type_name in self.outputparams: + print "\t%s, &%s, " % type_name, + print "G_TYPE_INVALID);" + + for code in self.returncode: + print code + + print "}\n" + + + def definegaimstructure(self, type): + if (self.headersonly) and (type[0] not in self.knowntypes): + print "struct _%s;" % type[0] + print "typedef struct _%s %s;" % (type[0], type[0]) + self.knowntypes.append(type[0]) + + def inputsimple(self, type, name): + self.paramshdr.append("%s %s" % (type[0], name)) + self.inputparams.append(("G_TYPE_INT", name)) + + def inputvalist(self, type, name): + self.paramshdr.append("va_list %s_NULL" % name) + + def inputstring(self, type, name): + self.paramshdr.append("const char *%s" % name) + self.inputparams.append(("G_TYPE_STRING", name)) + + def inputgaimstructure(self, type, name): + self.paramshdr.append("const %s *%s" % (type[0], name)) + self.inputparams.append(("G_TYPE_INT", "GPOINTER_TO_INT(%s)" % name)) + self.definegaimstructure(type) + + def inputpointer(self, type, name): + name += "_NULL" + self.paramshdr.append("const %s *%s" % (type[0], name)) + self.inputparams.append(("G_TYPE_INT", "0")) + + def inputhash(self, type, name): + self.paramshdr.append("const GHashTable *%s" % name) + self.inputparams.append(('dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, G_TYPE_STRING)', name)) + + def outputvoid(self, type, name): + self.functiontype = "void" + + def outputstring(self, type, name, const): + self.functiontype = "char*" + self.decls.append("char *%s = NULL;" % name) + self.outputparams.append(("G_TYPE_STRING", name)) +# self.returncode.append("NULLIFY(%s);" % name) + self.returncode.append("return %s;" % name); + + def outputsimple(self, type, name): + self.functiontype = type[0] + self.decls.append("%s %s = 0;" % (type[0], name)) + self.outputparams.append(("G_TYPE_INT", name)) + self.returncode.append("return %s;" % name); + + # we could add "const" to the return type but this would probably + # be a nuisance + def outputgaimstructure(self, type, name): + name = name + "_ID" + self.functiontype = "%s*" % type[0] + self.decls.append("int %s = 0;" % name) + self.outputparams.append(("G_TYPE_INT", "%s" % name)) + self.returncode.append("return (%s*) GINT_TO_POINTER(%s);" % (type[0], name)); + self.definegaimstructure(type) + + def outputlist(self, type, name): + self.functiontype = "%s*" % type[0] + self.decls.append("GArray *%s;" % name) + self.outputparams.append(('dbus_g_type_get_collection("GArray", G_TYPE_INT)', name)) + self.returncode.append("return garray_int_to_%s(%s);" % + (type[0].lower(), name)); + + +class ServerBinding (Binding): + def __init__(self, functiontext, paramtexts): + Binding.__init__(self, functiontext, paramtexts) + self.dparams = "" + self.cparams = [] + self.cdecls = [] + self.ccode = [] + self.cparamsout = [] + self.ccodeout = [] + self.argfunc = "dbus_message_get_args" + + def flush(self): + print "static DBusMessage*" + print "%s_DBUS(DBusMessage *message_DBUS, DBusError *error_DBUS) {" % \ + self.function.name + + print "\tDBusMessage *reply_DBUS;" + + for decl in self.cdecls: + print decl + + print "\t%s(message_DBUS, error_DBUS, " % self.argfunc, + for param in self.cparams: + print "DBUS_TYPE_%s, &%s," % param, + print "DBUS_TYPE_INVALID);" + + print "\tCHECK_ERROR(error_DBUS);" + + for code in self.ccode: + print code + + print "\treply_DBUS = dbus_message_new_method_return (message_DBUS);" + + print "\tdbus_message_append_args(reply_DBUS, ", + for param in self.cparamsout: + if type(param) is str: + print "%s, " % param + else: + print "DBUS_TYPE_%s, &%s, " % param, + print "DBUS_TYPE_INVALID);" + + for code in self.ccodeout: + print code + + print "\treturn reply_DBUS;\n}\n" + + + def addstring(self, *items): + for item in items: + self.dparams += item + r"\0" + + def addintype(self, type, name): + self.addstring("in", type, name) + + def addouttype(self, type, name): + self.addstring("out", type, name) + + + # input parameters + + def inputsimple(self, type, name): + self.cdecls.append("\tdbus_int32_t %s;" % name) + self.cparams.append(("INT32", name)) + self.addintype("i", name) + + def inputvalist(self, type, name): + self.cdecls.append("\tvoid * %s;" % name); + self.ccode.append("\t%s = NULL;" % name); + + def inputstring(self, type, name): + self.cdecls.append("\tconst char *%s;" % name) + self.cparams.append(("STRING", name)) + self.ccode .append("\tNULLIFY(%s);" % name) + self.addintype("s", name) + + def inputhash(self, type, name): + self.argfunc = "gaim_dbus_message_get_args" + self.cdecls.append("\tDBusMessageIter %s_ITER;" % name) + self.cdecls.append("\tGHashTable *%s;" % name) + self.cparams.append(("ARRAY", "%s_ITER" % name)) + self.ccode.append("\t%s = gaim_dbus_iter_hash_table(&%s_ITER, error_DBUS);" \ + % (name, name)) + self.ccode.append("\tCHECK_ERROR(error_DBUS);") + self.ccodeout.append("\tg_hash_table_destroy(%s);" % name) + self.addintype("a{ss}", name) + + def inputgaimstructure(self, type, name): + self.cdecls.append("\tdbus_int32_t %s_ID;" % name) + self.cdecls.append("\t%s *%s;" % (type[0], name)) + self.cparams.append(("INT32", name + "_ID")) + self.ccode.append("\tGAIM_DBUS_ID_TO_POINTER(%s, %s_ID, %s, error_DBUS);" % \ + (name, name, type[0])) + self.addintype("i", name) + + def inputpointer(self, type, name): + self.cdecls.append("\tdbus_int32_t %s_NULL;" % name) + self.cdecls .append("\t%s *%s;" % (type[0], name)) + self.cparams.append(("INT32", name + "_NULL")) + self.ccode .append("\t%s = NULL;" % name) + self.addintype("i", name) + + # output parameters + + def outputvoid(self, type, name): + self.ccode.append("\t%s;" % self.call) # just call the function + + def outputstring(self, type, name, const): + self.cdecls.append("\tconst char *%s;" % name) + self.ccode.append("\t%s = null_to_empty(%s);" % (name, self.call)) + self.cparamsout.append(("STRING", name)) + self.addouttype("s", name) + if not const: + self.ccodeout.append("\tg_free(%s);" % name) + + def outputsimple(self, type, name): + self.cdecls.append("\tdbus_int32_t %s;" % name) + self.ccode.append("\t%s = %s;" % (name, self.call)) + self.cparamsout.append(("INT32", name)) + self.addouttype("i", name) + + def outputgaimstructure(self, type, name): + self.cdecls.append("\tdbus_int32_t %s;" % name) + self.ccode .append("\tGAIM_DBUS_POINTER_TO_ID(%s, %s, error_DBUS);" % (name, self.call)) + self.cparamsout.append(("INT32", name)) + self.addouttype("i", name) + + # GList*, GSList*, assume that list is a list of objects + + # fixme: at the moment, we do NOT free the memory occupied by + # the list, we should free it if the list has NOT been declared const + + # fixme: we assume that this is a list of objects, not a list + # of strings + + def outputlist(self, type, name): + self.cdecls.append("\tdbus_int32_t %s_LEN;" % name) + self.ccodeout.append("\tg_free(%s);" % name) + + if self.function.name in stringlists: + self.cdecls.append("\tchar **%s;" % name) + self.ccode.append("\t%s = gaim_%s_to_array(%s, FALSE, &%s_LEN);" % \ + (name, type[0], self.call, name)) + self.cparamsout.append("\tDBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &%s, %s_LEN" \ + % (name, name)) + self.addouttype("as", name) + else: + self.cdecls.append("\tdbus_int32_t *%s;" % name) + self.ccode.append("\t%s = gaim_dbusify_%s(%s, FALSE, &%s_LEN);" % \ + (name, type[0], self.call, name)) + self.cparamsout.append("\tDBUS_TYPE_ARRAY, DBUS_TYPE_INT32, &%s, %s_LEN" \ + % (name, name)) + self.addouttype("ai", name) + + +class BindingSet: + regexp = r"^(\w[^()]*)\(([^()]*)\)\s*;\s*$"; + + def __init__(self, inputfile, fprefix): + self.inputiter = iter(inputfile) + self.functionregexp = \ + re.compile("^%s(\w[^()]*)\(([^()]*)\)\s*;\s*$" % fprefix) + + + + def process(self): + print "/* Generated by %s. Do not edit! */" % sys.argv[0] + + for line in self.inputiter: + words = line.split() + if len(words) == 0: # empty line + continue + if line[0] == "#": # preprocessor directive + continue + if words[0] in ["typedef", "struct", "enum", "static"]: + continue + + # accumulate lines until the parentheses are balance or an + # empty line has been encountered + myline = line.strip() + while myline.count("(") > myline.count(")"): + newline = self.inputiter.next().strip() + if len(newline) == 0: + break + myline += " " + newline + + # is this a function declaration? + thematch = self.functionregexp.match( + myline.replace("*", " " + pointer + " ")) + + if thematch is None: + continue + + functiontext = thematch.group(1) + paramstext = thematch.group(2).strip() + + if (paramstext == "void") or (paramstext == ""): + paramtexts = [] + else: + paramtexts = paramstext.split(",") + + try: + self.processfunction(functiontext, paramtexts) + except myexception: + sys.stderr.write(myline + "\n") + except: + sys.stderr.write(myline + "\n") + raise + + self.flush() + +class ServerBindingSet (BindingSet): + def __init__(self, inputfile, fprefix): + BindingSet.__init__(self, inputfile, fprefix) + self.functions = [] + + + def processfunction(self, functiontext, paramtexts): + binding = ServerBinding(functiontext, paramtexts) + binding.process() + self.functions.append((binding.function.name, binding.dparams)) + + def flush(self): + print "static GaimDBusBinding bindings_DBUS[] = { " + for function, params in self.functions: + print '{"%s", "%s", %s_DBUS},' % \ + (ctopascal(function), params, function) + + print "{NULL, NULL, NULL}" + print "};" + + print "#define GAIM_DBUS_REGISTER_BINDINGS(handle) gaim_dbus_register_bindings(handle, bindings_DBUS)" + +class ClientBindingSet (BindingSet): + def __init__(self, inputfile, fprefix, headersonly): + BindingSet.__init__(self, inputfile, fprefix) + self.functions = [] + self.knowntypes = [] + self.headersonly = headersonly + + def processfunction(self, functiontext, paramtexts): + binding = ClientBinding(functiontext, paramtexts, self.knowntypes, self.headersonly) + binding.process() + + def flush(self): + pass + +# Main program + +options = {} + +for arg in sys.argv[1:]: + if arg[0:2] == "--": + mylist = arg[2:].split("=",1) + command = mylist[0] + if len(mylist) > 1: + options[command] = mylist[1] + else: + options[command] = None + +if "export-only" in options: + fprefix = "DBUS_EXPORT\s+" +else: + fprefix = "" + +sys.stderr.write("%s: Functions not exported:\n" % sys.argv[0]) + +if "client" in options: + bindings = ClientBindingSet(sys.stdin, fprefix, + options.has_key("headers")) +else: + bindings = ServerBindingSet(sys.stdin, fprefix) +bindings.process() + + + + diff -r d10dda2777a9 -r b63ebf84c42b core/dbus-analyze-types.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/dbus-analyze-types.py Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,75 @@ +# This program takes a C header/source as the input and produces +# +# with --keyword=enum: the list of all enums +# with --keyword=struct: the list of all structs +# +# the output styles: +# +# --enum DBUS_POINTER_NAME1, +# DBUS_POINTER_NAME2, +# DBUS_POINTER_NAME3, +# +# --list NAME1 +# NAME2 +# NAME3 +# + + +import re +import sys + +options = {} + +def toprint(match, line): + if verbatim: + return line + else: + return pattern % match + +for arg in sys.argv[1:]: + if arg[0:2] == "--": + mylist = arg[2:].split("=",1) + command = mylist[0] + if len(mylist) > 1: + options[command] = mylist[1] + else: + options[command] = None + +keyword = options.get("keyword", "struct") +pattern = options.get("pattern", "%s") +verbatim = options.has_key("verbatim") + +structregexp1 = re.compile(r"^(typedef\s+)?%s\s+\w+\s+(\w+)\s*;" % keyword) +structregexp2 = re.compile(r"^(typedef\s+)?%s" % keyword) +structregexp3 = re.compile(r"^}\s+(\w+)\s*;") + +print "/* Generated by %s. Do not edit! */" % sys.argv[0] + +myinput = iter(sys.stdin) + +for line in myinput: + match = structregexp1.match(line) + if match is not None: + print toprint(match.group(2), line) + continue + + match = structregexp2.match(line) + if match is not None: + while True: + if verbatim: + print line.rstrip() + line = myinput.next() + match = structregexp3.match(line) + if match is not None: + print toprint(match.group(1), line) + break + if line[0] not in [" ", "\t", "{", "\n"]: + if verbatim: + print line + break + + + + + + diff -r d10dda2777a9 -r b63ebf84c42b core/dbus-bindings.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/dbus-bindings.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,113 @@ +/** + * @file dbus-bindings.h Gaim DBUS Bindings + * @ingroup core + * + * 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 + * + */ + +#ifndef _GAIM_DBUS_BINDINGS_H_ +#define _GAIM_DBUS_BINDINGS_H_ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +gint gaim_dbus_pointer_to_id(gpointer node); +gpointer gaim_dbus_id_to_pointer(gint id, GaimDBusType *type); +gint gaim_dbus_pointer_to_id_error(gpointer ptr, DBusError *error); +gpointer gaim_dbus_id_to_pointer_error(gint id, GaimDBusType *type, + const char *typename, DBusError *error); + +#define NULLIFY(id) id = empty_to_null(id) + +#define CHECK_ERROR(error) if (dbus_error_is_set(error)) return NULL; + +#define GAIM_DBUS_ID_TO_POINTER(ptr, id, type, error) \ + G_STMT_START { \ + ptr = (type*) gaim_dbus_id_to_pointer_error \ + (id, GAIM_DBUS_TYPE(type), #type, error); \ + CHECK_ERROR(error); \ + } G_STMT_END + + +#define GAIM_DBUS_POINTER_TO_ID(id, ptr, error) \ + G_STMT_START { \ + id = gaim_dbus_pointer_to_id_error(ptr,error); \ + CHECK_ERROR(error); \ + } G_STMT_END + + +dbus_bool_t +gaim_dbus_message_get_args (DBusMessage *message, + DBusError *error, + int first_arg_type, + ...); +dbus_bool_t +gaim_dbus_message_get_args_valist (DBusMessage *message, + DBusError *error, + int first_arg_type, + va_list var_args); + +dbus_bool_t +gaim_dbus_message_iter_get_args (DBusMessageIter *iter, + DBusError *error, + int first_arg_type, + ...); + +dbus_bool_t +gaim_dbus_message_iter_get_args_valist (DBusMessageIter *iter, + DBusError *error, + int first_arg_type, + va_list var_args); + +dbus_int32_t* gaim_dbusify_GList(GList *list, gboolean free_memory, + dbus_int32_t *len); +dbus_int32_t* gaim_dbusify_GSList(GSList *list, gboolean free_memory, + dbus_int32_t *len); +gpointer* gaim_GList_to_array(GList *list, gboolean free_memory, + dbus_int32_t *len); +gpointer* gaim_GSList_to_array(GSList *list, gboolean free_memory, + dbus_int32_t *len); +GHashTable *gaim_dbus_iter_hash_table(DBusMessageIter *iter, DBusError *error); + +const char* empty_to_null(const char *str); +const char* null_to_empty(const char *s); + +typedef struct { + const char *name; + const char *parameters; + DBusMessage* (*handler)(DBusMessage *request, DBusError *error); +} GaimDBusBinding; + +void gaim_dbus_register_bindings(void *handle, GaimDBusBinding *bindings); + +DBusConnection *gaim_dbus_get_connection(void); + +#ifdef __cplusplus +} +#endif + +#endif diff -r d10dda2777a9 -r b63ebf84c42b core/dbus-define-api.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/dbus-define-api.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,25 @@ +#error "This is file is not a valid C code" + +/* This file contains some of the macros from other header files as + function declarations. This does not make sense in C, but it + provides type information for the dbus-analyze-functions.py + program, which makes these macros callable by DBUS. */ + +/* blist.h */ +gboolean GAIM_BLIST_NODE_IS_CHAT(GaimBlistNode *node); +gboolean GAIM_BLIST_NODE_IS_BUDDY(GaimBlistNode *node); +gboolean GAIM_BLIST_NODE_IS_CONTACT(GaimBlistNode *node); +gboolean GAIM_BLIST_NODE_IS_GROUP(GaimBlistNode *node); +gboolean GAIM_BUDDY_IS_ONLINE(GaimBuddy *buddy); +gboolean GAIM_BLIST_NODE_HAS_FLAG(GaimBlistNode *node, int flags); +gboolean GAIM_BLIST_NODE_SHOULD_SAVE(GaimBlistNode *node); + +/* connection.h */ +gboolean GAIM_CONNECTION_IS_CONNECTED(GaimConnection *connection); +gboolean GAIM_CONNECTION_IS_VALID(GaimConnection *connection); + +/* conversation.h */ +GaimConvIm *GAIM_CONV_IM(const GaimConversation *conversation); +GaimConvIm *GAIM_CONV_CHAT(const GaimConversation *conversation); + + diff -r d10dda2777a9 -r b63ebf84c42b core/dbus-gaim.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/dbus-gaim.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,31 @@ +/* + * 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 + * + */ + +#ifndef _DBUS_GAIM_H_ +#define _DBUS_GAIM_H_ + +#define DBUS_SERVICE_GAIM "net.sf.gaim.GaimService" +#define DBUS_PATH_GAIM "/net/sf/gaim/GaimObject" +#define DBUS_INTERFACE_GAIM "net.sf.gaim.GaimInterface" + +#endif /* _DBUS_GAIM_H_ */ diff -r d10dda2777a9 -r b63ebf84c42b core/dbus-gaim.service --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/dbus-gaim.service Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,5 @@ +[D-BUS Service] +Name=net.sf.gaim.GaimService + +#replace this by the path to the gaim executable in your system +Exec=/home/pz215/projects/gaim/src/gaim diff -r d10dda2777a9 -r b63ebf84c42b core/dbus-maybe.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/dbus-maybe.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,28 @@ +/* This file contains macros that wrap calls to the gaim dbus module. + These macros call the appropriate functions if the build includes + dbus support and do nothing otherwise. See "dbus-server.h" for + documentation. */ + +#ifndef _GAIM_DBUS_MAYBE_H_ +#define _GAIM_DBUS_MAYBE_H_ + +#ifdef HAVE_DBUS + +#include "dbus-server.h" + +/* this provides a type check */ +#define GAIM_DBUS_REGISTER_POINTER(ptr, type) { \ + type *typed_ptr = ptr; \ + gaim_dbus_register_pointer(typed_ptr, GAIM_DBUS_TYPE(type)); \ +} +#define GAIM_DBUS_UNREGISTER_POINTER(ptr) gaim_dbus_unregister_pointer(ptr) + +#else /* !HAVE_DBUS */ + +#define GAIM_DBUS_REGISTER_POINTER(ptr, type) +#define GAIM_DBUS_UNREGISTER_POINTER(ptr) +#define DBUS_EXPORT + +#endif /* HAVE_DBUS */ + +#endif diff -r d10dda2777a9 -r b63ebf84c42b core/dbus-server.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/dbus-server.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,779 @@ +/* + * 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 + * + */ + +#define DBUS_API_SUBJECT_TO_CHANGE + +#include +#include +#include + +#include "account.h" +#include "blist.h" +#include "conversation.h" +#include "dbus-gaim.h" +#include "dbus-server.h" +#include "dbus-useful.h" +#include "dbus-bindings.h" +#include "debug.h" +#include "core.h" +#include "internal.h" +#include "savedstatuses.h" +#include "value.h" + + +/**************************************************************************/ +/** @name Gaim DBUS pointer registration mechanism */ +/**************************************************************************/ + +/* + * Here we include the list of #GAIM_DBUS_DEFINE_TYPE statements for + * all structs defined in gaim. This file has been generated by the + * #dbus-analyze-types.py script. + */ + +#include "dbus-types.c" + +/* + * The following three hashtables map are used to translate between + * pointers (nodes) and the corresponding handles (ids). + */ + +static GHashTable *map_node_id; +static GHashTable *map_id_node; +static GHashTable *map_id_type; + +static gchar *init_error; + +/** + * This function initializes the pointer-id traslation system. It + * creates the three above hashtables and defines parents of some types. + */ +void +gaim_dbus_init_ids(void) +{ + map_id_node = g_hash_table_new(g_direct_hash, g_direct_equal); + map_id_type = g_hash_table_new(g_direct_hash, g_direct_equal); + map_node_id = g_hash_table_new(g_direct_hash, g_direct_equal); + + GAIM_DBUS_TYPE(GaimBuddy)->parent = GAIM_DBUS_TYPE(GaimBlistNode); + GAIM_DBUS_TYPE(GaimContact)->parent = GAIM_DBUS_TYPE(GaimBlistNode); + GAIM_DBUS_TYPE(GaimChat)->parent = GAIM_DBUS_TYPE(GaimBlistNode); + GAIM_DBUS_TYPE(GaimGroup)->parent = GAIM_DBUS_TYPE(GaimBlistNode); +} + +void +gaim_dbus_register_pointer(gpointer node, GaimDBusType *type) +{ + static gint last_id = 0; + + g_return_if_fail(map_node_id); + g_return_if_fail(g_hash_table_lookup(map_node_id, node) == NULL); + + last_id++; + g_hash_table_insert(map_node_id, node, GINT_TO_POINTER(last_id)); + g_hash_table_insert(map_id_node, GINT_TO_POINTER(last_id), node); + g_hash_table_insert(map_id_type, GINT_TO_POINTER(last_id), type); +} + +void +gaim_dbus_unregister_pointer(gpointer node) +{ + gpointer id = g_hash_table_lookup(map_node_id, node); + + g_hash_table_remove(map_node_id, node); + g_hash_table_remove(map_id_node, GINT_TO_POINTER(id)); + g_hash_table_remove(map_id_type, GINT_TO_POINTER(id)); +} + +gint +gaim_dbus_pointer_to_id(gpointer node) +{ + gint id = GPOINTER_TO_INT(g_hash_table_lookup(map_node_id, node)); + if ((id == 0) && (node != NULL)) + { + gaim_debug_warning("dbus", + "Need to register an object with the dbus subsystem.\n"); + g_return_val_if_reached(0); + } + return id; +} + +gpointer +gaim_dbus_id_to_pointer(gint id, GaimDBusType *type) +{ + GaimDBusType *objtype; + + objtype = (GaimDBusType*)g_hash_table_lookup(map_id_type, + GINT_TO_POINTER(id)); + + while (objtype != type && objtype != NULL) + objtype = objtype->parent; + + if (objtype == type) + return g_hash_table_lookup(map_id_node, GINT_TO_POINTER(id)); + else + return NULL; +} + +gint +gaim_dbus_pointer_to_id_error(gpointer ptr, DBusError *error) +{ + gint id = gaim_dbus_pointer_to_id(ptr); + + if (ptr != NULL && id == 0) + dbus_set_error(error, "net.sf.gaim.ObjectNotFound", + "The return object is not mapped (this is a Gaim error)"); + + return id; +} + +gpointer +gaim_dbus_id_to_pointer_error(gint id, GaimDBusType *type, + const char *typename, DBusError *error) +{ + gpointer ptr = gaim_dbus_id_to_pointer(id, type); + + if (ptr == NULL && id != 0) + dbus_set_error(error, "net.sf.gaim.InvalidHandle", + "%s object with ID = %i not found", typename, id); + + return ptr; +} + + +/**************************************************************************/ +/** @name Modified versions of some DBus functions */ +/**************************************************************************/ + +dbus_bool_t +gaim_dbus_message_get_args(DBusMessage *message, + DBusError *error, int first_arg_type, ...) +{ + dbus_bool_t retval; + va_list var_args; + + va_start(var_args, first_arg_type); + retval = gaim_dbus_message_get_args_valist(message, error, first_arg_type, var_args); + va_end(var_args); + + return retval; +} + +dbus_bool_t +gaim_dbus_message_get_args_valist(DBusMessage *message, + DBusError *error, int first_arg_type, va_list var_args) +{ + DBusMessageIter iter; + + dbus_message_iter_init(message, &iter); + return gaim_dbus_message_iter_get_args_valist(&iter, error, first_arg_type, var_args); +} + +dbus_bool_t +gaim_dbus_message_iter_get_args(DBusMessageIter *iter, + DBusError *error, int first_arg_type, ...) +{ + dbus_bool_t retval; + va_list var_args; + + va_start(var_args, first_arg_type); + retval = gaim_dbus_message_iter_get_args_valist(iter, error, first_arg_type, var_args); + va_end(var_args); + + return retval; +} + +#define TYPE_IS_CONTAINER(typecode) \ + ((typecode) == DBUS_TYPE_STRUCT || \ + (typecode) == DBUS_TYPE_DICT_ENTRY || \ + (typecode) == DBUS_TYPE_VARIANT || \ + (typecode) == DBUS_TYPE_ARRAY) + + +dbus_bool_t +gaim_dbus_message_iter_get_args_valist(DBusMessageIter *iter, + DBusError *error, int first_arg_type, va_list var_args) +{ + int spec_type, msg_type, i; + + spec_type = first_arg_type; + + for (i = 0; spec_type != DBUS_TYPE_INVALID; i++) + { + msg_type = dbus_message_iter_get_arg_type(iter); + + if (msg_type != spec_type) + { + dbus_set_error(error, DBUS_ERROR_INVALID_ARGS, + "Argument %d is specified to be of type \"%i\", but " + "is actually of type \"%i\"\n", i, + spec_type, msg_type); + return FALSE; + } + + if (!TYPE_IS_CONTAINER(spec_type)) + { + gpointer ptr; + ptr = va_arg (var_args, gpointer); + dbus_message_iter_get_basic(iter, ptr); + } + else + { + DBusMessageIter *sub; + sub = va_arg (var_args, DBusMessageIter*); + dbus_message_iter_recurse(iter, sub); + gaim_debug_info("dbus", "subiter %p:%p\n", sub, * (gpointer*) sub); + break; /* for testing only! */ + } + + spec_type = va_arg(var_args, int); + if (!dbus_message_iter_next(iter) && spec_type != DBUS_TYPE_INVALID) + { + dbus_set_error (error, DBUS_ERROR_INVALID_ARGS, + "Message has only %d arguments, but more were expected", i); + return FALSE; + } + } + + return TRUE; +} + + + +/**************************************************************************/ +/** @name Useful functions */ +/**************************************************************************/ + +const char *empty_to_null(const char *str) +{ + if (str == NULL || str[0] == 0) + return NULL; + else + return str; +} + +const char * +null_to_empty(const char *s) +{ + if (s) + return s; + else + return ""; +} + +dbus_int32_t * +gaim_dbusify_GList(GList *list, gboolean free_memory, dbus_int32_t *len) +{ + dbus_int32_t *array; + int i; + GList *elem; + + *len = g_list_length(list); + array = g_new0(dbus_int32_t, g_list_length(list)); + for (i = 0, elem = list; elem != NULL; elem = elem->next, i++) + array[i] = gaim_dbus_pointer_to_id(elem->data); + + if (free_memory) + g_list_free(list); + + return array; +} + +dbus_int32_t * +gaim_dbusify_GSList(GSList *list, gboolean free_memory, dbus_int32_t *len) +{ + dbus_int32_t *array; + int i; + GSList *elem; + + *len = g_slist_length(list); + array = g_new0(dbus_int32_t, g_slist_length(list)); + for (i = 0, elem = list; elem != NULL; elem = elem->next, i++) + array[i] = gaim_dbus_pointer_to_id(elem->data); + + if (free_memory) + g_slist_free(list); + + return array; +} + +gpointer * +gaim_GList_to_array(GList *list, gboolean free_memory, dbus_int32_t *len) +{ + gpointer *array; + int i; + GList *elem; + + *len = g_list_length(list); + array = g_new0(gpointer, g_list_length(list)); + for (i = 0, elem = list; elem != NULL; elem = elem->next, i++) + array[i] = elem->data; + + if (free_memory) + g_list_free(list); + + return array; +} + +gpointer * +gaim_GSList_to_array(GSList *list, gboolean free_memory, dbus_int32_t *len) +{ + gpointer *array; + int i; + GSList *elem; + + *len = g_slist_length(list); + array = g_new0(gpointer, g_slist_length(list)); + for (i = 0, elem = list; elem != NULL; elem = elem->next, i++) + array[i] = elem->data; + + if (free_memory) + g_slist_free(list); + + return array; +} + +GHashTable * +gaim_dbus_iter_hash_table(DBusMessageIter *iter, DBusError *error) +{ + GHashTable *hash; + + /* we do not need to destroy strings because they are part of the message */ + hash = g_hash_table_new(g_str_hash, g_str_equal); + + do { + char *key, *value; + DBusMessageIter subiter; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_DICT_ENTRY) + goto error; + /* With all due respect to Dijkstra, + * this goto is for exception + * handling, and it is ok because it + * avoids duplication of the code + * responsible for destroying the hash + * table. Exceptional instructions + * for exceptional situations. + */ + + dbus_message_iter_recurse(iter, &subiter); + if (!gaim_dbus_message_iter_get_args(&subiter, error, + DBUS_TYPE_STRING, &key, + DBUS_TYPE_STRING, &value, + DBUS_TYPE_INVALID)) + goto error; /* same here */ + + g_hash_table_insert(hash, key, value); + } while (dbus_message_iter_next(iter)); + + return hash; + +error: + g_hash_table_destroy(hash); + return NULL; +} + +/**************************************************************/ +/* DBus bindings ... */ +/**************************************************************/ + +static DBusConnection *gaim_dbus_connection; + +DBusConnection * +gaim_dbus_get_connection(void) +{ + return gaim_dbus_connection; +} + +#include "dbus-bindings.c" + +static gboolean +gaim_dbus_dispatch_cb(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + const char *name; + GaimDBusBinding *bindings; + int i; + + bindings = (GaimDBusBinding*) user_data; + + if (!dbus_message_has_path(message, DBUS_PATH_GAIM)) + return FALSE; + + name = dbus_message_get_member(message); + + if (name == NULL) + return FALSE; + + if (dbus_message_get_type(message) != DBUS_MESSAGE_TYPE_METHOD_CALL) + return FALSE; + + for (i = 0; bindings[i].name; i++) + if (!strcmp(name, bindings[i].name)) + { + DBusMessage *reply; + DBusError error; + + dbus_error_init(&error); + + reply = bindings[i].handler(message, &error); + + if (reply == NULL && dbus_error_is_set(&error)) + reply = dbus_message_new_error (message, + error.name, error.message); + + if (reply != NULL) + { + dbus_connection_send(connection, reply, NULL); + dbus_message_unref(reply); + } + + return TRUE; /* return reply! */ + } + + return FALSE; +} + + +static const char * +dbus_gettext(const char **ptr) +{ + const char *text = *ptr; + *ptr += strlen(text) + 1; + return text; +} + +static void +gaim_dbus_introspect_cb(GList **bindings_list, void *bindings) +{ + *bindings_list = g_list_prepend(*bindings_list, bindings); +} + +static DBusMessage *gaim_dbus_introspect(DBusMessage *message) +{ + DBusMessage *reply; + GString *str; + GList *bindings_list, *node; + + str = g_string_sized_new(0x1000); /* TODO: why this size? */ + + g_string_append(str, "\n"); + g_string_append_printf(str, "\n", DBUS_PATH_GAIM); + g_string_append_printf(str, "\n", DBUS_INTERFACE_GAIM); + + bindings_list = NULL; + gaim_signal_emit(gaim_dbus_get_handle(), "dbus-introspect", &bindings_list); + + for (node = bindings_list; node; node = node->next) + { + GaimDBusBinding *bindings; + int i; + + bindings = (GaimDBusBinding*)node->data; + + for (i = 0; bindings[i].name; i++) + { + const char *text; + + g_string_append_printf(str, "\n", bindings[i].name); + + text = bindings[i].parameters; + while (*text) + { + const char *name, *direction, *type; + + direction = dbus_gettext(&text); + type = dbus_gettext(&text); + name = dbus_gettext(&text); + + g_string_append_printf(str, + "\n", + name, type, direction); + } + g_string_append(str, "\n"); + } + } + + g_string_append(str, "\n\n"); + + reply = dbus_message_new_method_return(message); + dbus_message_append_args(reply, DBUS_TYPE_STRING, &(str->str), + DBUS_TYPE_INVALID); + g_string_free(str, TRUE); + g_list_free(bindings_list); + + return reply; +} + +static DBusHandlerResult +gaim_dbus_dispatch(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + if (gaim_signal_emit_return_1(gaim_dbus_get_handle(), + "dbus-method-called", connection, message)) + return DBUS_HANDLER_RESULT_HANDLED; + + if (dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_METHOD_CALL && + dbus_message_has_path(message, DBUS_PATH_GAIM) && + dbus_message_has_interface(message, DBUS_INTERFACE_INTROSPECTABLE) && + dbus_message_has_member(message, "Introspect")) + { + DBusMessage *reply; + reply = gaim_dbus_introspect(message); + dbus_connection_send (connection, reply, NULL); + dbus_message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +void +gaim_dbus_register_bindings(void *handle, GaimDBusBinding *bindings) +{ + gaim_signal_connect(gaim_dbus_get_handle(), "dbus-method-called", + handle, + GAIM_CALLBACK(gaim_dbus_dispatch_cb), + bindings); + gaim_signal_connect(gaim_dbus_get_handle(), "dbus-introspect", + handle, + GAIM_CALLBACK(gaim_dbus_introspect_cb), + bindings); +} + +static void +gaim_dbus_dispatch_init(void) +{ + static DBusObjectPathVTable vtable = {NULL, &gaim_dbus_dispatch, NULL, NULL, NULL, NULL}; + DBusError error; + int result; + + dbus_error_init(&error); + gaim_dbus_connection = dbus_bus_get(DBUS_BUS_STARTER, &error); + + if (gaim_dbus_connection == NULL) + { + init_error = g_strdup_printf(N_("Failed to get connection: %s"), error.message); + dbus_error_free(&error); + return; + } + + if (!dbus_connection_register_object_path(gaim_dbus_connection, + DBUS_PATH_GAIM, &vtable, NULL)) + { + init_error = g_strdup_printf(N_("Failed to get name: %s"), error.name); + dbus_error_free(&error); + return; + } + + result = dbus_bus_request_name(gaim_dbus_connection, + DBUS_SERVICE_GAIM, 0, &error); + + if (dbus_error_is_set(&error)) + { + dbus_connection_unref(gaim_dbus_connection); + dbus_error_free(&error); + gaim_dbus_connection = NULL; + init_error = g_strdup_printf(N_("Failed to get serv name: %s"), error.name); + return; + } + + dbus_connection_setup_with_g_main(gaim_dbus_connection, NULL); + + gaim_debug_misc("dbus", "okkk\n"); + + gaim_signal_register(gaim_dbus_get_handle(), "dbus-method-called", + gaim_marshal_BOOLEAN__POINTER_POINTER, + gaim_value_new(GAIM_TYPE_BOOLEAN), 2, + gaim_value_new(GAIM_TYPE_POINTER), + gaim_value_new(GAIM_TYPE_POINTER)); + + gaim_signal_register(gaim_dbus_get_handle(), "dbus-introspect", + gaim_marshal_VOID__POINTER, NULL, 1, + gaim_value_new_outgoing(GAIM_TYPE_POINTER)); + + GAIM_DBUS_REGISTER_BINDINGS(gaim_dbus_get_handle()); +} + + + +/**************************************************************************/ +/** @name Signals */ +/**************************************************************************/ + + + +static char * +gaim_dbus_convert_signal_name(const char *gaim_name) +{ + int gaim_index, g_index; + char *g_name = g_new(char, strlen(gaim_name) + 1); + gboolean capitalize_next = TRUE; + + for (gaim_index = g_index = 0; gaim_name[gaim_index]; gaim_index++) + if (gaim_name[gaim_index] != '-' && gaim_name[gaim_index] != '_') + { + if (capitalize_next) + g_name[g_index++] = g_ascii_toupper(gaim_name[gaim_index]); + else + g_name[g_index++] = gaim_name[gaim_index]; + capitalize_next = FALSE; + } else + capitalize_next = TRUE; + + g_name[g_index] = 0; + + return g_name; +} + +#define my_arg(type) (ptr != NULL ? * ((type *)ptr) : va_arg(data, type)) + +static void +gaim_dbus_message_append_gaim_values(DBusMessageIter *iter, + int number, GaimValue **gaim_values, va_list data) +{ + int i; + + for (i = 0; i < number; i++) + { + const char *str; + int id; + gint xint; + guint xuint; + gboolean xboolean; + gpointer ptr = NULL; + + if (gaim_value_is_outgoing(gaim_values[i])) + { + ptr = my_arg(gpointer); + g_return_if_fail(ptr); + } + + switch (gaim_values[i]->type) + { + case GAIM_TYPE_INT: + xint = my_arg(gint); + dbus_message_iter_append_basic(iter, DBUS_TYPE_INT32, &xint); + break; + case GAIM_TYPE_UINT: + xuint = my_arg(guint); + dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &xuint); + break; + case GAIM_TYPE_BOOLEAN: + xboolean = my_arg(gboolean); + dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &xboolean); + break; + case GAIM_TYPE_STRING: + str = null_to_empty(my_arg(char*)); + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &str); + break; + case GAIM_TYPE_SUBTYPE: /* registered pointers only! */ + case GAIM_TYPE_POINTER: + case GAIM_TYPE_OBJECT: + case GAIM_TYPE_BOXED: + id = gaim_dbus_pointer_to_id(my_arg(gpointer)); + dbus_message_iter_append_basic(iter, + (sizeof(void *) == 4) ? DBUS_TYPE_UINT32 : DBUS_TYPE_UINT64, &id); + break; + default: /* no conversion implemented */ + g_return_if_reached(); + } + } +} + +#undef my_arg + +void +gaim_dbus_signal_emit_gaim(const char *name, int num_values, + GaimValue **values, va_list vargs) +{ + DBusMessage *signal; + DBusMessageIter iter; + char *newname; + +#if 0 /* this is noisy with no dbus connection */ + g_return_if_fail(gaim_dbus_connection); +#else + if (gaim_dbus_connection == NULL) + return; +#endif + + + /* + * The test below is a hack that prevents our "dbus-method-called" + * signal from being propagated to dbus. What we really need is a + * flag for each signal that states whether this signal is to be + * dbus-propagated or not. + */ + if (!strcmp(name, "dbus-method-called")) + return; + + newname = gaim_dbus_convert_signal_name(name); + signal = dbus_message_new_signal(DBUS_PATH_GAIM, DBUS_INTERFACE_GAIM, newname); + dbus_message_iter_init_append(signal, &iter); + + gaim_dbus_message_append_gaim_values(&iter, num_values, values, vargs); + + dbus_connection_send(gaim_dbus_connection, signal, NULL); + + g_free(newname); + dbus_message_unref(signal); +} + +const char * +gaim_dbus_get_init_error(void) +{ + return init_error; +} + +void * +gaim_dbus_get_handle(void) +{ + static int handle; + + return &handle; +} + +void +gaim_dbus_init(void) +{ + if (g_thread_supported()) + dbus_g_thread_init(); + + gaim_dbus_init_ids(); + + g_free(init_error); + init_error = NULL; + gaim_dbus_dispatch_init(); + if (init_error != NULL) + gaim_debug_error("dbus", "%s\n", init_error); +} + +void +gaim_dbus_uninit(void) +{ + /* Surely we must do SOME kind of uninitialization? */ + + g_free(init_error); + init_error = NULL; +} diff -r d10dda2777a9 -r b63ebf84c42b core/dbus-server.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/dbus-server.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,204 @@ +/** + * @file dbus-server.h Gaim DBUS Server + * @ingroup core + * + * 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 + * + */ + +#ifndef _GAIM_DBUS_SERVER_H_ +#define _GAIM_DBUS_SERVER_H_ + +#include "value.h" + + +G_BEGIN_DECLS + +/** + Types of pointers are identified by the ADDRESS of a GaimDbusType + object. This way, plugins can easily access types defined in gaim + proper as well as introduce their own types that will not conflict + with those introduced by other plugins. + + The structure GaimDbusType has only one element (GaimDBusType::parent), a + contains a pointer to the parent type, or @c NULL if the type has no + parent. Parent means the same as the base class in object oriented + programming. +*/ + +typedef struct _GaimDBusType GaimDBusType; + +struct _GaimDBusType { + GaimDBusType *parent; +}; + +/* By convention, the GaimDBusType variable representing each structure + GaimSomeStructure has the name GAIM_DBUS_TYPE_GaimSomeStructure. + The following macros facilitate defining such variables + + #GAIM_DBUS_DECLARE_TYPE declares an extern variable representing a + given type, for use in header files. + + #GAIM_DBUS_DEFINE_TYPE defines a variable representing a given + type, use in .c files. It defines a new type without a parent; for + types with a parent use #GAIM_DBUS_DEFINE_INHERITING_TYPE. + */ + +#define GAIM_DBUS_TYPE(type) (&GAIM_DBUS_TYPE_##type) + + +#define GAIM_DBUS_DECLARE_TYPE(type) \ + extern GaimDBusType GAIM_DBUS_TYPE_##type; + +#define GAIM_DBUS_DEFINE_TYPE(type) \ + GaimDBusType GAIM_DBUS_TYPE_##type = { NULL }; + +#define GAIM_DBUS_DEFINE_INHERITING_TYPE(type, parent) \ + GaimDBusType GAIM_DBUS_TYPE_##type = { GAIM_DBUS_TYPE(parent) }; + +#define GAIM_DBUS_RETURN_FALSE_IF_DISABLED(plugin) \ + if (gaim_dbus_get_init_error() != NULL) \ + { \ + gchar *title; \ + title = g_strdup_printf("Unable to Load %s Plugin", plugin->info->name); \ + gaim_notify_error(NULL, title, \ + _("Gaim's D-BUS server is not running for the reason listed below"), \ + _(gaim_dbus_get_init_error())); \ + g_free(title); \ + return FALSE; \ + } + +/** + Initializes gaim dbus pointer registration engine. + + Remote dbus applications need a way of addressing objects exposed + by gaim to the outside world. In gaim itself, these objects (such + as GaimBuddy and company) are identified by pointers. The gaim + dbus pointer registration engine converts pointers to handles and + back. + + In order for an object to participate in the scheme, it must + register itself and its type with the engine. This registration + allocates an integer id which can be resolved to the pointer and + back. + + Handles are not persistent. They are reissued every time gaim is + started. This is not good; external applications that use gaim + should work even whether gaim was restarted in the middle of the + interaction. + + Pointer registration is only a temporary solution. When GaimBuddy + and similar structures have been converted into gobjects, this + registration will be done automatically by objects themselves. + + By the way, this kind of object-handle translation should be so + common that there must be a library (maybe even glib) that + implements it. I feel a bit like reinventing the wheel here. +*/ +void gaim_dbus_init_ids(void); + +/** + Registers a typed pointer. + + @param node The pointer to register. + @param type Type of that pointer. + */ +void gaim_dbus_register_pointer(gpointer node, GaimDBusType *type); + +/** + Unregisters a pointer previously registered with + gaim_dbus_register_pointer. + + @param node The pointer to register. + */ +void gaim_dbus_unregister_pointer(gpointer node); + + + +/** + Emits a dbus signal. + + @param name The name of the signal ("bla-bla-blaa") + @param num_values The number of parameters. + @param values Array of pointers to #GaimValue objects representing + the types of the parameters. + @param vargs A va_list containing the actual parameters. + */ +void gaim_dbus_signal_emit_gaim(const char *name, int num_values, + GaimValue **values, va_list vargs); + +/** + * Returns whether Gaim's D-BUS subsystem is up and running. If it's + * NOT running then gaim_dbus_dispatch_init() failed for some reason, + * and a message should have been gaim_debug_error()'ed. + * + * Gaim plugins that use D-BUS should use the + * GAIM_DBUS_RETURN_FALSE_IF_DISABLED macro to short-circuit + * initialization if Gaim's D-BUS subsystem is not running. + * + * @return If the D-BUS subsystem started with no problems then this + * will return NULL and everything will be hunky dory. If + * there was an error initializing the D-BUS subsystem then + * this will return an error message explaining why. + */ +const char *gaim_dbus_get_init_error(void); + +/** + * Returns the dbus subsystem handle. + * + * @return The dbus subsystem handle. + */ +void *gaim_dbus_get_handle(void); + +/** + * Starts Gaim's D-BUS server. It is responsible for handling DBUS + * requests from other applications. + */ +void gaim_dbus_init(void); + +/** + * Uninitializes Gaim's D-BUS server. + */ +void gaim_dbus_uninit(void); + +/** + + Macro #DBUS_EXPORT expands to nothing. It is used to indicate to the + dbus-analize-functions.py script that the given function should be + available to other applications through DBUS. If + dbus-analize-functions.py is run without the "--export-only" option, + this prefix is ignored. + + */ + +#define DBUS_EXPORT + +/* + Here we include the list of #GAIM_DBUS_DECLARE_TYPE statements for + all structs defined in gaim. This file has been generated by the + #dbus-analize-types.py script. +*/ + +#include "dbus-types.h" + +G_END_DECLS + +#endif /* _GAIM_DBUS_SERVER_H_ */ diff -r d10dda2777a9 -r b63ebf84c42b core/dbus-useful.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/dbus-useful.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,53 @@ +#include +#include + +#include "dbus-useful.h" +#include "conversation.h" +#include "util.h" + + +GaimAccount * +gaim_accounts_find_ext(const char *name, const char *protocol_id, + gboolean (*account_test)(const GaimAccount *account)) +{ + GaimAccount *result = NULL; + GList *l; + char *who; + + if (name) + who = g_strdup(gaim_normalize(NULL, name)); + else + who = NULL; + + for (l = gaim_accounts_get_all(); l != NULL; l = l->next) { + GaimAccount *account = (GaimAccount *)l->data; + + if (who && strcmp(gaim_normalize(NULL, gaim_account_get_username(account)), who)) + continue; + + if (protocol_id && strcmp(account->protocol_id, protocol_id)) + continue; + + if (account_test && !account_test(account)) + continue; + + result = account; + break; + } + + g_free(who); + + return result; +} + +GaimAccount *gaim_accounts_find_any(const char *name, const char *protocol) +{ + return gaim_accounts_find_ext(name, protocol, NULL); +} + +GaimAccount *gaim_accounts_find_connected(const char *name, const char *protocol) +{ + return gaim_accounts_find_ext(name, protocol, gaim_account_is_connected); +} + + diff -r d10dda2777a9 -r b63ebf84c42b core/dbus-useful.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/dbus-useful.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,13 @@ +#include "conversation.h" + +GaimAccount *gaim_accounts_find_ext(const char *name, const char *protocol_id, + gboolean (*account_test)(const GaimAccount *account)); + +GaimAccount *gaim_accounts_find_any(const char *name, const char *protocol); + +GaimAccount *gaim_accounts_find_connected(const char *name, const char *protocol); + + + + + diff -r d10dda2777a9 -r b63ebf84c42b core/debug.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/debug.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,198 @@ +/** + * @file debug.c Debug API + * @ingroup core + * + * 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 "debug.h" +#include "internal.h" +#include "prefs.h" +#include "util.h" + +static GaimDebugUiOps *debug_ui_ops = NULL; + +/* + * This determines whether debug info should be written to the + * console or not. + * + * It doesn't make sense to make this a normal Gaim preference + * because it's a command line option. This will always be FALSE, + * unless the user explicitly started Gaim with the -d flag. + * It doesn't matter what this value was the last time Gaim was + * started, so it doesn't make sense to save it in prefs. + */ +static gboolean debug_enabled = FALSE; + +static void +gaim_debug_vargs(GaimDebugLevel level, const char *category, + const char *format, va_list args) +{ + GaimDebugUiOps *ops; + char *arg_s = NULL; + + g_return_if_fail(level != GAIM_DEBUG_ALL); + g_return_if_fail(format != NULL); + + ops = gaim_debug_get_ui_ops(); + + if (!debug_enabled && ((ops == NULL) || (ops->print == NULL))) + return; + + arg_s = g_strdup_vprintf(format, args); + + if (debug_enabled) { + gchar *ts_s; + + if ((category != NULL) && + (gaim_prefs_exists("/core/debug/timestamps")) && + (gaim_prefs_get_bool("/core/debug/timestamps"))) { + const char *mdate; + + time_t mtime = time(NULL); + mdate = gaim_utf8_strftime("%H:%M:%S", localtime(&mtime)); + ts_s = g_strdup_printf("(%s) ", mdate); + } else { + ts_s = g_strdup(""); + } + + if (category == NULL) + g_print("%s%s", ts_s, arg_s); + else + g_print("%s%s: %s", ts_s, category, arg_s); + + g_free(ts_s); + } + + if (ops != NULL && ops->print != NULL) + ops->print(level, category, arg_s); + + g_free(arg_s); +} + +void +gaim_debug(GaimDebugLevel level, const char *category, + const char *format, ...) +{ + va_list args; + + g_return_if_fail(level != GAIM_DEBUG_ALL); + g_return_if_fail(format != NULL); + + va_start(args, format); + gaim_debug_vargs(level, category, format, args); + va_end(args); +} + +void +gaim_debug_misc(const char *category, const char *format, ...) +{ + va_list args; + + g_return_if_fail(format != NULL); + + va_start(args, format); + gaim_debug_vargs(GAIM_DEBUG_MISC, category, format, args); + va_end(args); +} + +void +gaim_debug_info(const char *category, const char *format, ...) +{ + va_list args; + + g_return_if_fail(format != NULL); + + va_start(args, format); + gaim_debug_vargs(GAIM_DEBUG_INFO, category, format, args); + va_end(args); +} + +void +gaim_debug_warning(const char *category, const char *format, ...) +{ + va_list args; + + g_return_if_fail(format != NULL); + + va_start(args, format); + gaim_debug_vargs(GAIM_DEBUG_WARNING, category, format, args); + va_end(args); +} + +void +gaim_debug_error(const char *category, const char *format, ...) +{ + va_list args; + + g_return_if_fail(format != NULL); + + va_start(args, format); + gaim_debug_vargs(GAIM_DEBUG_ERROR, category, format, args); + va_end(args); +} + +void +gaim_debug_fatal(const char *category, const char *format, ...) +{ + va_list args; + + g_return_if_fail(format != NULL); + + va_start(args, format); + gaim_debug_vargs(GAIM_DEBUG_FATAL, category, format, args); + va_end(args); +} + +void +gaim_debug_set_enabled(gboolean enabled) +{ + debug_enabled = enabled; +} + +gboolean +gaim_debug_is_enabled() +{ + return debug_enabled; +} + +void +gaim_debug_set_ui_ops(GaimDebugUiOps *ops) +{ + debug_ui_ops = ops; +} + +GaimDebugUiOps * +gaim_debug_get_ui_ops(void) +{ + return debug_ui_ops; +} + +void +gaim_debug_init(void) +{ + gaim_prefs_add_none("/core/debug"); + + /* + * This pref is currently used by both the console + * output and the debug window output. + */ + gaim_prefs_add_bool("/core/debug/timestamps", FALSE); +} diff -r d10dda2777a9 -r b63ebf84c42b core/debug.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/debug.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,191 @@ +/** + * @file debug.h Debug API + * @ingroup core + * + * 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 + */ +#ifndef _GAIM_DEBUG_H_ +#define _GAIM_DEBUG_H_ + +#include +#include + +/** + * Debug levels. + */ +typedef enum +{ + GAIM_DEBUG_ALL = 0, /**< All debug levels. */ + GAIM_DEBUG_MISC, /**< General chatter. */ + GAIM_DEBUG_INFO, /**< General operation Information. */ + GAIM_DEBUG_WARNING, /**< Warnings. */ + GAIM_DEBUG_ERROR, /**< Errors. */ + GAIM_DEBUG_FATAL /**< Fatal errors. */ + +} GaimDebugLevel; + +/** + * Debug UI operations. + */ +typedef struct +{ + void (*print)(GaimDebugLevel level, const char *category, + const char *arg_s); +} GaimDebugUiOps; + +#ifdef __cplusplus +extern "C" { +#endif + +/**************************************************************************/ +/** @name Debug API */ +/**************************************************************************/ +/** + * Outputs debug information. + * + * @param level The debug level. + * @param category The category (or @c NULL). + * @param format The format string. + */ +void gaim_debug(GaimDebugLevel level, const char *category, + const char *format, ...); + +/** + * Outputs misc. level debug information. + * + * This is a wrapper for gaim_debug(), and uses GAIM_DEBUG_MISC as + * the level. + * + * @param category The category (or @c NULL). + * @param format The format string. + * + * @see gaim_debug() + */ +void gaim_debug_misc(const char *category, const char *format, ...); + +/** + * Outputs info level debug information. + * + * This is a wrapper for gaim_debug(), and uses GAIM_DEBUG_INFO as + * the level. + * + * @param category The category (or @c NULL). + * @param format The format string. + * + * @see gaim_debug() + */ +void gaim_debug_info(const char *category, const char *format, ...); + +/** + * Outputs warning level debug information. + * + * This is a wrapper for gaim_debug(), and uses GAIM_DEBUG_WARNING as + * the level. + * + * @param category The category (or @c NULL). + * @param format The format string. + * + * @see gaim_debug() + */ +void gaim_debug_warning(const char *category, const char *format, ...); + +/** + * Outputs error level debug information. + * + * This is a wrapper for gaim_debug(), and uses GAIM_DEBUG_ERROR as + * the level. + * + * @param category The category (or @c NULL). + * @param format The format string. + * + * @see gaim_debug() + */ +void gaim_debug_error(const char *category, const char *format, ...); + +/** + * Outputs fatal error level debug information. + * + * This is a wrapper for gaim_debug(), and uses GAIM_DEBUG_ERROR as + * the level. + * + * @param category The category (or @c NULL). + * @param format The format string. + * + * @see gaim_debug() + */ +void gaim_debug_fatal(const char *category, const char *format, ...); + +/** + * Enable or disable printing debug output to the console. + * + * @param enabled TRUE to enable debug output or FALSE to disable it. + */ +void gaim_debug_set_enabled(gboolean enabled); + +/** + * Check if console debug output is enabled. + * + * @return TRUE if debuggin is enabled, FALSE if it is not. + */ +gboolean gaim_debug_is_enabled(void); + +/*@}*/ + +/**************************************************************************/ +/** @name UI Registration Functions */ +/**************************************************************************/ +/*@{*/ + +/** + * Sets the UI operations structure to be used when outputting debug + * information. + * + * @param ops The UI operations structure. + */ +void gaim_debug_set_ui_ops(GaimDebugUiOps *ops); + +/** + * Returns the UI operations structure used when outputting debug + * information. + * + * @return The UI operations structure in use. + */ +GaimDebugUiOps *gaim_debug_get_ui_ops(void); + +/*@}*/ + +/**************************************************************************/ +/** @name Debug Subsystem */ +/**************************************************************************/ +/*@{*/ + +/** + * Initializes the debug subsystem. + */ +void gaim_debug_init(void); + +/*@}*/ + +#ifdef __cplusplus +} +#endif + +#endif /* _GAIM_DEBUG_H_ */ diff -r d10dda2777a9 -r b63ebf84c42b core/desktopitem.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/desktopitem.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,1272 @@ +/** + * @file gaim-desktop-item.c Functions for managing .desktop files + * @ingroup core + * + * 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 + * + */ + +/* + * The following code has been adapted from gnome-desktop-item.[ch], + * as found on gnome-desktop-2.8.1. + * + * Copyright (C) 2004 by Alceste Scalas . + * + * Original copyright notice: + * + * Copyright (C) 1999, 2000 Red Hat Inc. + * Copyright (C) 2001 Sid Vicious + * All rights reserved. + * + * This file is part of the Gnome Library. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include "desktopitem.h" +#include "internal.h" + +struct _GaimDesktopItem { + int refcount; + + /* all languages used */ + GList *languages; + + GaimDesktopItemType type; + + /* `modified' means that the ditem has been + * modified since the last save. */ + gboolean modified; + + /* Keys of the main section only */ + GList *keys; + + GList *sections; + + /* This includes ALL keys, including + * other sections, separated by '/' */ + GHashTable *main_hash; + + char *location; + + time_t mtime; +}; + +typedef struct { + char *name; + GList *keys; +} Section; + +typedef enum { + ENCODING_UNKNOWN, + ENCODING_UTF8, + ENCODING_LEGACY_MIXED +} Encoding; + +/************************************************************************** + * Private utility functions + **************************************************************************/ +static GaimDesktopItemType +type_from_string (const char *type) +{ + if (!type) + return GAIM_DESKTOP_ITEM_TYPE_NULL; + + switch (type [0]) { + case 'A': + if (!strcmp (type, "Application")) + return GAIM_DESKTOP_ITEM_TYPE_APPLICATION; + break; + case 'L': + if (!strcmp (type, "Link")) + return GAIM_DESKTOP_ITEM_TYPE_LINK; + break; + case 'F': + if (!strcmp (type, "FSDevice")) + return GAIM_DESKTOP_ITEM_TYPE_FSDEVICE; + break; + case 'M': + if (!strcmp (type, "MimeType")) + return GAIM_DESKTOP_ITEM_TYPE_MIME_TYPE; + break; + case 'D': + if (!strcmp (type, "Directory")) + return GAIM_DESKTOP_ITEM_TYPE_DIRECTORY; + break; + case 'S': + if (!strcmp (type, "Service")) + return GAIM_DESKTOP_ITEM_TYPE_SERVICE; + + else if (!strcmp (type, "ServiceType")) + return GAIM_DESKTOP_ITEM_TYPE_SERVICE_TYPE; + break; + default: + break; + } + + return GAIM_DESKTOP_ITEM_TYPE_OTHER; +} + +static Section * +find_section (GaimDesktopItem *item, const char *section) +{ + GList *li; + Section *sec; + + if (section == NULL) + return NULL; + if (strcmp (section, "Desktop Entry") == 0) + return NULL; + + for (li = item->sections; li != NULL; li = li->next) { + sec = li->data; + if (strcmp (sec->name, section) == 0) + return sec; + } + + sec = g_new0 (Section, 1); + sec->name = g_strdup (section); + sec->keys = NULL; + + item->sections = g_list_append (item->sections, sec); + + /* Don't mark the item modified, this is just an empty section, + * it won't be saved even */ + + return sec; +} + +static Section * +section_from_key (GaimDesktopItem *item, const char *key) +{ + char *p; + char *name; + Section *sec; + + if (key == NULL) + return NULL; + + p = strchr (key, '/'); + if (p == NULL) + return NULL; + + name = g_strndup (key, p - key); + + sec = find_section (item, name); + + g_free (name); + + return sec; +} + +static const char * +key_basename (const char *key) +{ + char *p = strrchr (key, '/'); + if (p != NULL) + return p+1; + else + return key; +} + +static void +set (GaimDesktopItem *item, const char *key, const char *value) +{ + Section *sec = section_from_key (item, key); + + if (sec != NULL) { + if (value != NULL) { + if (g_hash_table_lookup (item->main_hash, key) == NULL) + sec->keys = g_list_append + (sec->keys, + g_strdup (key_basename (key))); + + g_hash_table_replace (item->main_hash, + g_strdup (key), + g_strdup (value)); + } else { + GList *list = g_list_find_custom + (sec->keys, key_basename (key), + (GCompareFunc)strcmp); + if (list != NULL) { + g_free (list->data); + sec->keys = + g_list_delete_link (sec->keys, list); + } + g_hash_table_remove (item->main_hash, key); + } + } else { + if (value != NULL) { + if (g_hash_table_lookup (item->main_hash, key) == NULL) + item->keys = g_list_append (item->keys, + g_strdup (key)); + + g_hash_table_replace (item->main_hash, + g_strdup (key), + g_strdup (value)); + } else { + GList *list = g_list_find_custom + (item->keys, key, (GCompareFunc)strcmp); + if (list != NULL) { + g_free (list->data); + item->keys = + g_list_delete_link (item->keys, list); + } + g_hash_table_remove (item->main_hash, key); + } + } + item->modified = TRUE; +} + + +static void +_gaim_desktop_item_set_string (GaimDesktopItem *item, + const char *attr, + const char *value) +{ + g_return_if_fail (item != NULL); + g_return_if_fail (item->refcount > 0); + g_return_if_fail (attr != NULL); + + set (item, attr, value); + + if (strcmp (attr, GAIM_DESKTOP_ITEM_TYPE) == 0) + item->type = type_from_string (value); +} + +static GaimDesktopItem * +_gaim_desktop_item_new (void) +{ + GaimDesktopItem *retval; + + retval = g_new0 (GaimDesktopItem, 1); + + retval->refcount++; + + retval->main_hash = g_hash_table_new_full (g_str_hash, g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_free); + + /* These are guaranteed to be set */ + _gaim_desktop_item_set_string (retval, + GAIM_DESKTOP_ITEM_NAME, + _("No name")); + _gaim_desktop_item_set_string (retval, + GAIM_DESKTOP_ITEM_ENCODING, + "UTF-8"); + _gaim_desktop_item_set_string (retval, + GAIM_DESKTOP_ITEM_VERSION, + "1.0"); + + return retval; +} + +static gpointer +_gaim_desktop_item_copy (gpointer boxed) +{ + return gaim_desktop_item_copy (boxed); +} + +static void +_gaim_desktop_item_free (gpointer boxed) +{ + gaim_desktop_item_unref (boxed); +} + +/* Note, does not include the trailing \n */ +static char * +my_fgets (char *buf, gsize bufsize, FILE *df) +{ + int c; + gsize pos; + + g_return_val_if_fail (buf != NULL, NULL); + g_return_val_if_fail (df != NULL, NULL); + + pos = 0; + buf[0] = '\0'; + + do { + c = getc (df); + if (c == EOF || c == '\n') + break; + buf[pos++] = c; + } while (pos < bufsize-1); + + if (c == EOF && pos == 0) + return NULL; + + buf[pos++] = '\0'; + + return buf; +} + +static Encoding +get_encoding (FILE *df) +{ + gboolean old_kde = FALSE; + char buf [BUFSIZ]; + gboolean all_valid_utf8 = TRUE; + + while (my_fgets (buf, sizeof (buf), df) != NULL) { + if (strncmp (GAIM_DESKTOP_ITEM_ENCODING, + buf, + strlen (GAIM_DESKTOP_ITEM_ENCODING)) == 0) { + char *p = &buf[strlen (GAIM_DESKTOP_ITEM_ENCODING)]; + if (*p == ' ') + p++; + if (*p != '=') + continue; + p++; + if (*p == ' ') + p++; + if (strcmp (p, "UTF-8") == 0) { + return ENCODING_UTF8; + } else if (strcmp (p, "Legacy-Mixed") == 0) { + return ENCODING_LEGACY_MIXED; + } else { + /* According to the spec we're not supposed + * to read a file like this */ + return ENCODING_UNKNOWN; + } + } else if (strcmp ("[KDE Desktop Entry]", buf) == 0) { + old_kde = TRUE; + /* don't break yet, we still want to support + * Encoding even here */ + } + if (all_valid_utf8 && ! g_utf8_validate (buf, -1, NULL)) + all_valid_utf8 = FALSE; + } + + if (old_kde) + return ENCODING_LEGACY_MIXED; + + /* A dilemma, new KDE files are in UTF-8 but have no Encoding + * info, at this time we really can't tell. The best thing to + * do right now is to just assume UTF-8 if the whole file + * validates as utf8 I suppose */ + + if (all_valid_utf8) + return ENCODING_UTF8; + else + return ENCODING_LEGACY_MIXED; +} + +static char * +snarf_locale_from_key (const char *key) +{ + const char *brace; + char *locale, *p; + + brace = strchr (key, '['); + if (brace == NULL) + return NULL; + + locale = g_strdup (brace + 1); + if (*locale == '\0') { + g_free (locale); + return NULL; + } + p = strchr (locale, ']'); + if (p == NULL) { + g_free (locale); + return NULL; + } + *p = '\0'; + return locale; +} + +static gboolean +check_locale (const char *locale) +{ + GIConv cd = g_iconv_open ("UTF-8", locale); + if ((GIConv)-1 == cd) + return FALSE; + g_iconv_close (cd); + return TRUE; +} + +static void +insert_locales (GHashTable *encodings, char *enc, ...) +{ + va_list args; + char *s; + + va_start (args, enc); + for (;;) { + s = va_arg (args, char *); + if (s == NULL) + break; + g_hash_table_insert (encodings, s, enc); + } + va_end (args); +} + +/* make a standard conversion table from the desktop standard spec */ +static GHashTable * +init_encodings (void) +{ + GHashTable *encodings = g_hash_table_new (g_str_hash, g_str_equal); + + /* "C" is plain ascii */ + insert_locales (encodings, "ASCII", "C", NULL); + + insert_locales (encodings, "ARMSCII-8", "by", NULL); + insert_locales (encodings, "BIG5", "zh_TW", NULL); + insert_locales (encodings, "CP1251", "be", "bg", NULL); + if (check_locale ("EUC-CN")) { + insert_locales (encodings, "EUC-CN", "zh_CN", NULL); + } else { + insert_locales (encodings, "GB2312", "zh_CN", NULL); + } + insert_locales (encodings, "EUC-JP", "ja", NULL); + insert_locales (encodings, "EUC-KR", "ko", NULL); + /*insert_locales (encodings, "GEORGIAN-ACADEMY", NULL);*/ + insert_locales (encodings, "GEORGIAN-PS", "ka", NULL); + insert_locales (encodings, "ISO-8859-1", "br", "ca", "da", "de", "en", "es", "eu", "fi", "fr", "gl", "it", "nl", "wa", "no", "pt", "pt", "sv", NULL); + insert_locales (encodings, "ISO-8859-2", "cs", "hr", "hu", "pl", "ro", "sk", "sl", "sq", "sr", NULL); + insert_locales (encodings, "ISO-8859-3", "eo", NULL); + insert_locales (encodings, "ISO-8859-5", "mk", "sp", NULL); + insert_locales (encodings, "ISO-8859-7", "el", NULL); + insert_locales (encodings, "ISO-8859-9", "tr", NULL); + insert_locales (encodings, "ISO-8859-13", "lt", "lv", "mi", NULL); + insert_locales (encodings, "ISO-8859-14", "ga", "cy", NULL); + insert_locales (encodings, "ISO-8859-15", "et", NULL); + insert_locales (encodings, "KOI8-R", "ru", NULL); + insert_locales (encodings, "KOI8-U", "uk", NULL); + if (check_locale ("TCVN-5712")) { + insert_locales (encodings, "TCVN-5712", "vi", NULL); + } else { + insert_locales (encodings, "TCVN", "vi", NULL); + } + insert_locales (encodings, "TIS-620", "th", NULL); + /*insert_locales (encodings, "VISCII", NULL);*/ + + return encodings; +} + +static const char * +get_encoding_from_locale (const char *locale) +{ + char lang[3]; + const char *encoding; + static GHashTable *encodings = NULL; + + if (locale == NULL) + return NULL; + + /* if locale includes encoding, use it */ + encoding = strchr (locale, '.'); + if (encoding != NULL) { + return encoding+1; + } + + if (encodings == NULL) + encodings = init_encodings (); + + /* first try the entire locale (at this point ll_CC) */ + encoding = g_hash_table_lookup (encodings, locale); + if (encoding != NULL) + return encoding; + + /* Try just the language */ + strncpy (lang, locale, 2); + lang[2] = '\0'; + return g_hash_table_lookup (encodings, lang); +} + +static char * +decode_string_and_dup (const char *s) +{ + char *p = g_malloc (strlen (s) + 1); + char *q = p; + + do { + if (*s == '\\'){ + switch (*(++s)){ + case 's': + *p++ = ' '; + break; + case 't': + *p++ = '\t'; + break; + case 'n': + *p++ = '\n'; + break; + case '\\': + *p++ = '\\'; + break; + case 'r': + *p++ = '\r'; + break; + default: + *p++ = '\\'; + *p++ = *s; + break; + } + } else { + *p++ = *s; + } + } while (*s++); + + return q; +} + +static char * +decode_string (const char *value, Encoding encoding, const char *locale) +{ + char *retval = NULL; + + /* if legacy mixed, then convert */ + if (locale != NULL && encoding == ENCODING_LEGACY_MIXED) { + const char *char_encoding = get_encoding_from_locale (locale); + char *utf8_string; + if (char_encoding == NULL) + return NULL; + if (strcmp (char_encoding, "ASCII") == 0) { + return decode_string_and_dup (value); + } + utf8_string = g_convert (value, -1, "UTF-8", char_encoding, + NULL, NULL, NULL); + if (utf8_string == NULL) + return NULL; + retval = decode_string_and_dup (utf8_string); + g_free (utf8_string); + return retval; + /* if utf8, then validate */ + } else if (locale != NULL && encoding == ENCODING_UTF8) { + if ( ! g_utf8_validate (value, -1, NULL)) + /* invalid utf8, ignore this key */ + return NULL; + return decode_string_and_dup (value); + } else { + /* Meaning this is not a localized string */ + return decode_string_and_dup (value); + } +} + +/************************************************************ + * Parser: * + ************************************************************/ + +static gboolean G_GNUC_CONST +standard_is_boolean (const char * key) +{ + static GHashTable *bools = NULL; + + if (bools == NULL) { + bools = g_hash_table_new (g_str_hash, g_str_equal); + g_hash_table_insert (bools, + GAIM_DESKTOP_ITEM_NO_DISPLAY, + GAIM_DESKTOP_ITEM_NO_DISPLAY); + g_hash_table_insert (bools, + GAIM_DESKTOP_ITEM_HIDDEN, + GAIM_DESKTOP_ITEM_HIDDEN); + g_hash_table_insert (bools, + GAIM_DESKTOP_ITEM_TERMINAL, + GAIM_DESKTOP_ITEM_TERMINAL); + g_hash_table_insert (bools, + GAIM_DESKTOP_ITEM_READ_ONLY, + GAIM_DESKTOP_ITEM_READ_ONLY); + } + + return g_hash_table_lookup (bools, key) != NULL; +} + +static gboolean G_GNUC_CONST +standard_is_strings (const char *key) +{ + static GHashTable *strings = NULL; + + if (strings == NULL) { + strings = g_hash_table_new (g_str_hash, g_str_equal); + g_hash_table_insert (strings, + GAIM_DESKTOP_ITEM_FILE_PATTERN, + GAIM_DESKTOP_ITEM_FILE_PATTERN); + g_hash_table_insert (strings, + GAIM_DESKTOP_ITEM_ACTIONS, + GAIM_DESKTOP_ITEM_ACTIONS); + g_hash_table_insert (strings, + GAIM_DESKTOP_ITEM_MIME_TYPE, + GAIM_DESKTOP_ITEM_MIME_TYPE); + g_hash_table_insert (strings, + GAIM_DESKTOP_ITEM_PATTERNS, + GAIM_DESKTOP_ITEM_PATTERNS); + g_hash_table_insert (strings, + GAIM_DESKTOP_ITEM_SORT_ORDER, + GAIM_DESKTOP_ITEM_SORT_ORDER); + } + + return g_hash_table_lookup (strings, key) != NULL; +} + +/* If no need to cannonize, returns NULL */ +static char * +cannonize (const char *key, const char *value) +{ + if (standard_is_boolean (key)) { + if (value[0] == 'T' || + value[0] == 't' || + value[0] == 'Y' || + value[0] == 'y' || + atoi (value) != 0) { + return g_strdup ("true"); + } else { + return g_strdup ("false"); + } + } else if (standard_is_strings (key)) { + int len = strlen (value); + if (len == 0 || value[len-1] != ';') { + return g_strconcat (value, ";", NULL); + } + } + /* XXX: Perhaps we should canonize numeric values as well, but this + * has caused some subtle problems before so it needs to be done + * carefully if at all */ + return NULL; +} + +static void +insert_key (GaimDesktopItem *item, + Section *cur_section, + Encoding encoding, + const char *key, + const char *value, + gboolean old_kde, + gboolean no_translations) +{ + char *k; + char *val; + /* we always store everything in UTF-8 */ + if (cur_section == NULL && + strcmp (key, GAIM_DESKTOP_ITEM_ENCODING) == 0) { + k = g_strdup (key); + val = g_strdup ("UTF-8"); + } else { + char *locale = snarf_locale_from_key (key); + /* If we're ignoring translations */ + if (no_translations && locale != NULL) { + g_free (locale); + return; + } + val = decode_string (value, encoding, locale); + + /* Ignore this key, it's whacked */ + if (val == NULL) { + g_free (locale); + return; + } + + g_strchomp (val); + + /* For old KDE entries, we can also split by a comma + * on sort order, so convert to semicolons */ + if (old_kde && + cur_section == NULL && + strcmp (key, GAIM_DESKTOP_ITEM_SORT_ORDER) == 0 && + strchr (val, ';') == NULL) { + int i; + for (i = 0; val[i] != '\0'; i++) { + if (val[i] == ',') + val[i] = ';'; + } + } + + /* Check some types, not perfect, but catches a lot + * of things */ + if (cur_section == NULL) { + char *cannon = cannonize (key, val); + if (cannon != NULL) { + g_free (val); + val = cannon; + } + } + + k = g_strdup (key); + + /* Take care of the language part */ + if (locale != NULL && + strcmp (locale, "C") == 0) { + char *p; + /* Whack C locale */ + p = strchr (k, '['); + if(p) *p = '\0'; + g_free (locale); + } else if (locale != NULL) { + char *p, *brace; + + /* Whack the encoding part */ + p = strchr (locale, '.'); + if (p != NULL) + *p = '\0'; + + if (g_list_find_custom (item->languages, locale, + (GCompareFunc)strcmp) == NULL) { + item->languages = g_list_prepend + (item->languages, locale); + } else { + g_free (locale); + } + + /* Whack encoding from encoding in the key */ + brace = strchr (k, '['); + if(brace != NULL) { + p = strchr (brace, '.'); + if (p != NULL) { + *p = ']'; + *(p+1) = '\0'; + } + } + } + } + + + if (cur_section == NULL) { + /* only add to list if we haven't seen it before */ + if (g_hash_table_lookup (item->main_hash, k) == NULL) { + item->keys = g_list_prepend (item->keys, + g_strdup (k)); + } + /* later duplicates override earlier ones */ + g_hash_table_replace (item->main_hash, k, val); + } else { + char *full = g_strdup_printf + ("%s/%s", + cur_section->name, k); + /* only add to list if we haven't seen it before */ + if (g_hash_table_lookup (item->main_hash, full) == NULL) { + cur_section->keys = + g_list_prepend (cur_section->keys, k); + } + /* later duplicates override earlier ones */ + g_hash_table_replace (item->main_hash, + full, val); + } +} + +static const char * +lookup (const GaimDesktopItem *item, const char *key) +{ + return g_hash_table_lookup (item->main_hash, key); +} + +static void +setup_type (GaimDesktopItem *item, const char *uri) +{ + const char *type = g_hash_table_lookup (item->main_hash, + GAIM_DESKTOP_ITEM_TYPE); + if (type == NULL && uri != NULL) { + char *base = g_path_get_basename (uri); + if (base != NULL && + strcmp (base, ".directory") == 0) { + /* This gotta be a directory */ + g_hash_table_replace (item->main_hash, + g_strdup (GAIM_DESKTOP_ITEM_TYPE), + g_strdup ("Directory")); + item->keys = g_list_prepend + (item->keys, g_strdup (GAIM_DESKTOP_ITEM_TYPE)); + item->type = GAIM_DESKTOP_ITEM_TYPE_DIRECTORY; + } else { + item->type = GAIM_DESKTOP_ITEM_TYPE_NULL; + } + g_free (base); + } else { + item->type = type_from_string (type); + } +} + +static const char * +lookup_locale (const GaimDesktopItem *item, const char *key, const char *locale) +{ + if (locale == NULL || + strcmp (locale, "C") == 0) { + return lookup (item, key); + } else { + const char *ret; + char *full = g_strdup_printf ("%s[%s]", key, locale); + ret = lookup (item, full); + g_free (full); + return ret; + } +} + +/* fallback to find something suitable for C locale */ +static char * +try_english_key (GaimDesktopItem *item, const char *key) +{ + char *str; + char *locales[] = { "en_US", "en_GB", "en_AU", "en", NULL }; + int i; + + str = NULL; + for (i = 0; locales[i] != NULL && str == NULL; i++) { + str = g_strdup (lookup_locale (item, key, locales[i])); + } + if (str != NULL) { + /* We need a 7-bit ascii string, so whack all + * above 127 chars */ + guchar *p; + for (p = (guchar *)str; *p != '\0'; p++) { + if (*p > 127) + *p = '?'; + } + } + return str; +} + + +static void +sanitize (GaimDesktopItem *item, const char *uri) +{ + const char *type; + + type = lookup (item, GAIM_DESKTOP_ITEM_TYPE); + + /* understand old gnome style url exec thingies */ + if (type != NULL && strcmp (type, "URL") == 0) { + const char *exec = lookup (item, GAIM_DESKTOP_ITEM_EXEC); + set (item, GAIM_DESKTOP_ITEM_TYPE, "Link"); + if (exec != NULL) { + /* Note, this must be in this order */ + set (item, GAIM_DESKTOP_ITEM_URL, exec); + set (item, GAIM_DESKTOP_ITEM_EXEC, NULL); + } + } + + /* we make sure we have Name, Encoding and Version */ + if (lookup (item, GAIM_DESKTOP_ITEM_NAME) == NULL) { + char *name = try_english_key (item, GAIM_DESKTOP_ITEM_NAME); + /* If no name, use the basename */ + if (name == NULL && uri != NULL) + name = g_path_get_basename (uri); + /* If no uri either, use same default as gnome_desktop_item_new */ + if (name == NULL) + name = g_strdup (_("No name")); + g_hash_table_replace (item->main_hash, + g_strdup (GAIM_DESKTOP_ITEM_NAME), + name); + item->keys = g_list_prepend + (item->keys, g_strdup (GAIM_DESKTOP_ITEM_NAME)); + } + if (lookup (item, GAIM_DESKTOP_ITEM_ENCODING) == NULL) { + /* We store everything in UTF-8 so write that down */ + g_hash_table_replace (item->main_hash, + g_strdup (GAIM_DESKTOP_ITEM_ENCODING), + g_strdup ("UTF-8")); + item->keys = g_list_prepend + (item->keys, g_strdup (GAIM_DESKTOP_ITEM_ENCODING)); + } + if (lookup (item, GAIM_DESKTOP_ITEM_VERSION) == NULL) { + /* this is the version that we follow, so write it down */ + g_hash_table_replace (item->main_hash, + g_strdup (GAIM_DESKTOP_ITEM_VERSION), + g_strdup ("1.0")); + item->keys = g_list_prepend + (item->keys, g_strdup (GAIM_DESKTOP_ITEM_VERSION)); + } +} + +enum { + FirstBrace, + OnSecHeader, + IgnoreToEOL, + IgnoreToEOLFirst, + KeyDef, + KeyDefOnKey, + KeyValue +}; + +static GaimDesktopItem * +ditem_load (FILE *df, + gboolean no_translations, + const char *uri) +{ + int state; + char CharBuffer [1024]; + char *next = CharBuffer; + int c; + Encoding encoding; + GaimDesktopItem *item; + Section *cur_section = NULL; + char *key = NULL; + gboolean old_kde = FALSE; + + encoding = get_encoding (df); + if (encoding == ENCODING_UNKNOWN) { + fclose(df); + /* spec says, don't read this file */ + printf ("Unknown encoding of .desktop file"); + + return NULL; + } + + /* Rewind since get_encoding goes through the file */ + if (fseek(df, 0L, SEEK_SET)) { + fclose(df); + /* spec says, don't read this file */ + printf ("fseek() error on .desktop file"); + return NULL; + } + + item = _gaim_desktop_item_new (); + item->modified = FALSE; + + /* Note: location and mtime are filled in by the new_from_file + * function since it has those values */ + +#define GAIM_DESKTOP_ITEM_OVERFLOW (next == &CharBuffer [sizeof(CharBuffer)-1]) + + state = FirstBrace; + while ((c = getc (df)) != EOF) { + if (c == '\r') /* Ignore Carriage Return */ + continue; + + switch (state) { + + case OnSecHeader: + if (c == ']' || GAIM_DESKTOP_ITEM_OVERFLOW) { + *next = '\0'; + next = CharBuffer; + + /* keys were inserted in reverse */ + if (cur_section != NULL && + cur_section->keys != NULL) { + cur_section->keys = g_list_reverse + (cur_section->keys); + } + if (strcmp (CharBuffer, + "KDE Desktop Entry") == 0) { + /* Main section */ + cur_section = NULL; + old_kde = TRUE; + } else if (strcmp (CharBuffer, + "Desktop Entry") == 0) { + /* Main section */ + cur_section = NULL; + } else { + cur_section = g_new0 (Section, 1); + cur_section->name = + g_strdup (CharBuffer); + cur_section->keys = NULL; + item->sections = g_list_prepend + (item->sections, cur_section); + } + state = IgnoreToEOL; + } else if (c == '[') { + /* FIXME: probably error out instead of ignoring this */ + } else { + *next++ = c; + } + break; + + case IgnoreToEOL: + case IgnoreToEOLFirst: + if (c == '\n'){ + if (state == IgnoreToEOLFirst) + state = FirstBrace; + else + state = KeyDef; + next = CharBuffer; + } + break; + + case FirstBrace: + case KeyDef: + case KeyDefOnKey: + if (c == '#') { + if (state == FirstBrace) + state = IgnoreToEOLFirst; + else + state = IgnoreToEOL; + break; + } + + if (c == '[' && state != KeyDefOnKey){ + state = OnSecHeader; + next = CharBuffer; + g_free (key); + key = NULL; + break; + } + /* On first pass, don't allow dangling keys */ + if (state == FirstBrace) + break; + + if ((c == ' ' && state != KeyDefOnKey) || c == '\t') + break; + + if (c == '\n' || GAIM_DESKTOP_ITEM_OVERFLOW) { /* Abort Definition */ + next = CharBuffer; + state = KeyDef; + break; + } + + if (c == '=' || GAIM_DESKTOP_ITEM_OVERFLOW){ + *next = '\0'; + + g_free (key); + key = g_strdup (CharBuffer); + state = KeyValue; + next = CharBuffer; + } else { + *next++ = c; + state = KeyDefOnKey; + } + break; + + case KeyValue: + if (GAIM_DESKTOP_ITEM_OVERFLOW || c == '\n'){ + *next = '\0'; + + insert_key (item, cur_section, encoding, + key, CharBuffer, old_kde, + no_translations); + + g_free (key); + key = NULL; + + state = (c == '\n') ? KeyDef : IgnoreToEOL; + next = CharBuffer; + } else { + *next++ = c; + } + break; + + } /* switch */ + + } /* while ((c = getc_unlocked (f)) != EOF) */ + if (c == EOF && state == KeyValue) { + *next = '\0'; + + insert_key (item, cur_section, encoding, + key, CharBuffer, old_kde, + no_translations); + + g_free (key); + key = NULL; + } + +#undef GAIM_DESKTOP_ITEM_OVERFLOW + + /* keys were inserted in reverse */ + if (cur_section != NULL && + cur_section->keys != NULL) { + cur_section->keys = g_list_reverse (cur_section->keys); + } + /* keys were inserted in reverse */ + item->keys = g_list_reverse (item->keys); + /* sections were inserted in reverse */ + item->sections = g_list_reverse (item->sections); + + /* sanitize some things */ + sanitize (item, uri); + + /* make sure that we set up the type */ + setup_type (item, uri); + + fclose (df); + + return item; +} + +static void +copy_string_hash (gpointer key, gpointer value, gpointer user_data) +{ + GHashTable *copy = user_data; + g_hash_table_replace (copy, + g_strdup (key), + g_strdup (value)); +} + +static void +free_section (gpointer data, gpointer user_data) +{ + Section *section = data; + + g_free (section->name); + section->name = NULL; + + g_list_foreach (section->keys, (GFunc)g_free, NULL); + g_list_free (section->keys); + section->keys = NULL; + + g_free (section); +} + +static Section * +dup_section (Section *sec) +{ + GList *li; + Section *retval = g_new0 (Section, 1); + + retval->name = g_strdup (sec->name); + + retval->keys = g_list_copy (sec->keys); + for (li = retval->keys; li != NULL; li = li->next) + li->data = g_strdup (li->data); + + return retval; +} + +/************************************************************************** + * Public functions + **************************************************************************/ +GaimDesktopItem * +gaim_desktop_item_new_from_file (const char *filename) +{ + GaimDesktopItem *retval; + FILE *dfile; + + g_return_val_if_fail (filename != NULL, NULL); + + dfile = g_fopen(filename, "r"); + if (!dfile) { + printf ("Can't open %s: %s", filename, strerror(errno)); + return NULL; + } + + retval = ditem_load(dfile, FALSE, filename); + + return retval; +} + +GaimDesktopItemType +gaim_desktop_item_get_entry_type (const GaimDesktopItem *item) +{ + g_return_val_if_fail (item != NULL, 0); + g_return_val_if_fail (item->refcount > 0, 0); + + return item->type; +} + +const char * +gaim_desktop_item_get_string (const GaimDesktopItem *item, + const char *attr) +{ + g_return_val_if_fail (item != NULL, NULL); + g_return_val_if_fail (item->refcount > 0, NULL); + g_return_val_if_fail (attr != NULL, NULL); + + return lookup (item, attr); +} + +GaimDesktopItem * +gaim_desktop_item_copy (const GaimDesktopItem *item) +{ + GList *li; + GaimDesktopItem *retval; + + g_return_val_if_fail (item != NULL, NULL); + g_return_val_if_fail (item->refcount > 0, NULL); + + retval = _gaim_desktop_item_new (); + + retval->type = item->type; + retval->modified = item->modified; + retval->location = g_strdup (item->location); + retval->mtime = item->mtime; + + /* Languages */ + retval->languages = g_list_copy (item->languages); + for (li = retval->languages; li != NULL; li = li->next) + li->data = g_strdup (li->data); + + /* Keys */ + retval->keys = g_list_copy (item->keys); + for (li = retval->keys; li != NULL; li = li->next) + li->data = g_strdup (li->data); + + /* Sections */ + retval->sections = g_list_copy (item->sections); + for (li = retval->sections; li != NULL; li = li->next) + li->data = dup_section (li->data); + + retval->main_hash = g_hash_table_new_full (g_str_hash, g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_free); + + g_hash_table_foreach (item->main_hash, + copy_string_hash, + retval->main_hash); + + return retval; +} + +void +gaim_desktop_item_unref (GaimDesktopItem *item) +{ + g_return_if_fail (item != NULL); + g_return_if_fail (item->refcount > 0); + + item->refcount--; + + if(item->refcount != 0) + return; + + g_list_foreach (item->languages, (GFunc)g_free, NULL); + g_list_free (item->languages); + item->languages = NULL; + + g_list_foreach (item->keys, (GFunc)g_free, NULL); + g_list_free (item->keys); + item->keys = NULL; + + g_list_foreach (item->sections, free_section, NULL); + g_list_free (item->sections); + item->sections = NULL; + + g_hash_table_destroy (item->main_hash); + item->main_hash = NULL; + + g_free (item->location); + item->location = NULL; + + g_free (item); +} + +GType +gaim_desktop_item_get_type (void) +{ + static GType type = 0; + + if (type == 0) { + type = g_boxed_type_register_static ("GaimDesktopItem", + _gaim_desktop_item_copy, + _gaim_desktop_item_free); + } + + return type; +} diff -r d10dda2777a9 -r b63ebf84c42b core/desktopitem.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/desktopitem.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,171 @@ +/** + * @file desktopitem.h Functions for managing .desktop files + * @ingroup core + * + * 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 + * + */ + +/* + * The following code has been adapted from gnome-desktop-item.[ch], + * as found on gnome-desktop-2.8.1. + * + * Copyright (C) 2004 by Alceste Scalas . + * + * Original copyright notice: + * + * Copyright (C) 1999, 2000 Red Hat Inc. + * Copyright (C) 2001 Sid Vicious + * All rights reserved. + * + * This file is part of the Gnome Library. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef _GAIM_DESKTOP_ITEM_H_ +#define _GAIM_DESKTOP_ITEM_H_ + +#include +#include + +G_BEGIN_DECLS + +typedef enum { + GAIM_DESKTOP_ITEM_TYPE_NULL = 0 /* This means its NULL, that is, not + * set */, + GAIM_DESKTOP_ITEM_TYPE_OTHER /* This means it's not one of the below + strings types, and you must get the + Type attribute. */, + + /* These are the standard compliant types: */ + GAIM_DESKTOP_ITEM_TYPE_APPLICATION, + GAIM_DESKTOP_ITEM_TYPE_LINK, + GAIM_DESKTOP_ITEM_TYPE_FSDEVICE, + GAIM_DESKTOP_ITEM_TYPE_MIME_TYPE, + GAIM_DESKTOP_ITEM_TYPE_DIRECTORY, + GAIM_DESKTOP_ITEM_TYPE_SERVICE, + GAIM_DESKTOP_ITEM_TYPE_SERVICE_TYPE +} GaimDesktopItemType; + +typedef struct _GaimDesktopItem GaimDesktopItem; + +#define GAIM_TYPE_DESKTOP_ITEM (gaim_desktop_item_get_type ()) +GType gaim_desktop_item_get_type (void); + +/* standard */ +#define GAIM_DESKTOP_ITEM_ENCODING "Encoding" /* string */ +#define GAIM_DESKTOP_ITEM_VERSION "Version" /* numeric */ +#define GAIM_DESKTOP_ITEM_NAME "Name" /* localestring */ +#define GAIM_DESKTOP_ITEM_GENERIC_NAME "GenericName" /* localestring */ +#define GAIM_DESKTOP_ITEM_TYPE "Type" /* string */ +#define GAIM_DESKTOP_ITEM_FILE_PATTERN "FilePattern" /* regexp(s) */ +#define GAIM_DESKTOP_ITEM_TRY_EXEC "TryExec" /* string */ +#define GAIM_DESKTOP_ITEM_NO_DISPLAY "NoDisplay" /* boolean */ +#define GAIM_DESKTOP_ITEM_COMMENT "Comment" /* localestring */ +#define GAIM_DESKTOP_ITEM_EXEC "Exec" /* string */ +#define GAIM_DESKTOP_ITEM_ACTIONS "Actions" /* strings */ +#define GAIM_DESKTOP_ITEM_ICON "Icon" /* string */ +#define GAIM_DESKTOP_ITEM_MINI_ICON "MiniIcon" /* string */ +#define GAIM_DESKTOP_ITEM_HIDDEN "Hidden" /* boolean */ +#define GAIM_DESKTOP_ITEM_PATH "Path" /* string */ +#define GAIM_DESKTOP_ITEM_TERMINAL "Terminal" /* boolean */ +#define GAIM_DESKTOP_ITEM_TERMINAL_OPTIONS "TerminalOptions" /* string */ +#define GAIM_DESKTOP_ITEM_SWALLOW_TITLE "SwallowTitle" /* string */ +#define GAIM_DESKTOP_ITEM_SWALLOW_EXEC "SwallowExec" /* string */ +#define GAIM_DESKTOP_ITEM_MIME_TYPE "MimeType" /* regexp(s) */ +#define GAIM_DESKTOP_ITEM_PATTERNS "Patterns" /* regexp(s) */ +#define GAIM_DESKTOP_ITEM_DEFAULT_APP "DefaultApp" /* string */ +#define GAIM_DESKTOP_ITEM_DEV "Dev" /* string */ +#define GAIM_DESKTOP_ITEM_FS_TYPE "FSType" /* string */ +#define GAIM_DESKTOP_ITEM_MOUNT_POINT "MountPoint" /* string */ +#define GAIM_DESKTOP_ITEM_READ_ONLY "ReadOnly" /* boolean */ +#define GAIM_DESKTOP_ITEM_UNMOUNT_ICON "UnmountIcon" /* string */ +#define GAIM_DESKTOP_ITEM_SORT_ORDER "SortOrder" /* strings */ +#define GAIM_DESKTOP_ITEM_URL "URL" /* string */ +#define GAIM_DESKTOP_ITEM_DOC_PATH "X-GNOME-DocPath" /* string */ + +/** + * This function loads 'filename' and turns it into a GaimDesktopItem. + * + * @param filename The filename or directory path to load the GaimDesktopItem from + * + * @return The newly loaded item, or NULL on error. + */ +GaimDesktopItem *gaim_desktop_item_new_from_file (const char *filename); + +/** + * Gets the type attribute (the 'Type' field) of the item. This should + * usually be 'Application' for an application, but it can be 'Directory' + * for a directory description. There are other types available as well. + * The type usually indicates how the desktop item should be handeled and + * how the 'Exec' field should be handeled. + * + * @param item A desktop item + * + * @return The type of the specified 'item'. The returned memory + * remains owned by the GaimDesktopItem and should not be freed. + */ +GaimDesktopItemType gaim_desktop_item_get_entry_type (const GaimDesktopItem *item); + +/** + * Gets the value of an attribute of the item, as a string. + * + * @param item A desktop item + * @param attr The attribute to look for + * + * @return The value of the specified item attribute. + */ +const char *gaim_desktop_item_get_string (const GaimDesktopItem *item, + const char *attr); + +/** + * Creates a copy of a GaimDesktopItem. The new copy has a refcount of 1. + * Note: Section stack is NOT copied. + * + * @param item The item to be copied + * + * @return The new copy + */ +GaimDesktopItem *gaim_desktop_item_copy (const GaimDesktopItem *item); + +/** + * Decreases the reference count of the specified item, and destroys + * the item if there are no more references left. + * + * @param item A desktop item + */ +void gaim_desktop_item_unref (GaimDesktopItem *item); + +G_END_DECLS + +#endif /* _GAIM_DESKTOP_ITEM_H_ */ diff -r d10dda2777a9 -r b63ebf84c42b core/dnsquery.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/dnsquery.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,713 @@ +/** + * @file dnsquery.c DNS query API + * @ingroup core + * + * 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 "debug.h" +#include "notify.h" +#include "prefs.h" +#include "dnsquery.h" +#include "util.h" + +/************************************************************************** + * DNS query API + **************************************************************************/ + +struct _GaimDnsQueryData { +}; + +#if defined(__unix__) || defined(__APPLE__) + +#define MAX_DNS_CHILDREN 4 + +/* + * This structure represents both a pending DNS request and + * a free child process. + */ +typedef struct { + char *host; + int port; + GaimDnsQueryConnectFunction callback; + gpointer data; + guint inpa; + int fd_in, fd_out; + pid_t dns_pid; +} pending_dns_request_t; + +static GSList *free_dns_children = NULL; +static GQueue *queued_requests = NULL; + +static int number_of_dns_children = 0; + +typedef struct { + char hostname[512]; + int port; +} dns_params_t; + +typedef struct { + dns_params_t params; + GaimDnsQueryConnectFunction callback; + gpointer data; +} queued_dns_request_t; + +/* + * Begin the DNS resolver child process functions. + */ +#ifdef HAVE_SIGNAL_H +static void +trap_gdb_bug() +{ + const char *message = + "Gaim's DNS child got a SIGTRAP signal.\n" + "This can be caused by trying to run gaim inside gdb.\n" + "There is a known gdb bug which prevents this. Supposedly gaim\n" + "should have detected you were using gdb and used an ugly hack,\n" + "check cope_with_gdb_brokenness() in dnsquery.c.\n\n" + "For more info about this bug, see http://sources.redhat.com/ml/gdb/2001-07/msg00349.html\n"; + fputs("\n* * *\n",stderr); + fputs(message,stderr); + fputs("* * *\n\n",stderr); + execlp("xmessage","xmessage","-center", message, NULL); + _exit(1); +} +#endif + +static void +cope_with_gdb_brokenness() +{ +#ifdef __linux__ + static gboolean already_done = FALSE; + char s[256], e[512]; + int n; + pid_t ppid; + + if(already_done) + return; + already_done = TRUE; + ppid = getppid(); + snprintf(s, sizeof(s), "/proc/%d/exe", ppid); + n = readlink(s, e, sizeof(e)); + if(n < 0) + return; + + e[MIN(n,sizeof(e)-1)] = '\0'; + + if(strstr(e,"gdb")) { + gaim_debug_info("dns", + "Debugger detected, performing useless query...\n"); + gethostbyname("x.x.x.x.x"); + } +#endif +} + +static void +gaim_dns_resolverthread(int child_out, int child_in, gboolean show_debug) +{ + dns_params_t dns_params; + const size_t zero = 0; + int rc; +#ifdef HAVE_GETADDRINFO + struct addrinfo hints, *res, *tmp; + char servname[20]; +#else + struct sockaddr_in sin; + const size_t addrlen = sizeof(sin); +#endif + +#ifdef HAVE_SIGNAL_H + signal(SIGHUP, SIG_DFL); + signal(SIGINT, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + signal(SIGCHLD, SIG_DFL); + signal(SIGTERM, SIG_DFL); + signal(SIGTRAP, trap_gdb_bug); +#endif + + /* + * We resolve 1 host name for each iteration of this + * while loop. + * + * The top half of this reads in the hostname and port + * number from the socket with our parent. The bottom + * half of this resolves the IP (blocking) and sends + * the result back to our parent, when finished. + */ + while (1) { + const char ch = 'Y'; + fd_set fds; + struct timeval tv = { .tv_sec = 40 , .tv_usec = 0 }; + FD_ZERO(&fds); + FD_SET(child_in, &fds); + rc = select(child_in + 1, &fds, NULL, NULL, &tv); + if (!rc) { + if (show_debug) + printf("dns[%d]: nobody needs me... =(\n", getpid()); + break; + } + rc = read(child_in, &dns_params, sizeof(dns_params_t)); + if (rc < 0) { + perror("read()"); + break; + } + if (rc == 0) { + if (show_debug) + printf("dns[%d]: Oops, father has gone, wait for me, wait...!\n", getpid()); + _exit(0); + } + if (dns_params.hostname[0] == '\0') { + printf("dns[%d]: hostname = \"\" (port = %d)!!!\n", getpid(), dns_params.port); + _exit(1); + } + /* Tell our parent that we read the data successfully */ + write(child_out, &ch, sizeof(ch)); + + /* We have the hostname and port, now resolve the IP */ + +#ifdef HAVE_GETADDRINFO + g_snprintf(servname, sizeof(servname), "%d", dns_params.port); + memset(&hints, 0, sizeof(hints)); + + /* This is only used to convert a service + * name to a port number. As we know we are + * passing a number already, we know this + * value will not be really used by the C + * library. + */ + hints.ai_socktype = SOCK_STREAM; + rc = getaddrinfo(dns_params.hostname, servname, &hints, &res); + write(child_out, &rc, sizeof(rc)); + if (rc != 0) { + close(child_out); + if (show_debug) + printf("dns[%d] Error: getaddrinfo returned %d\n", + getpid(), rc); + dns_params.hostname[0] = '\0'; + continue; + } + tmp = res; + while (res) { + size_t ai_addrlen = res->ai_addrlen; + write(child_out, &ai_addrlen, sizeof(ai_addrlen)); + write(child_out, res->ai_addr, res->ai_addrlen); + res = res->ai_next; + } + freeaddrinfo(tmp); + write(child_out, &zero, sizeof(zero)); +#else + if (!inet_aton(dns_params.hostname, &sin.sin_addr)) { + struct hostent *hp; + if (!(hp = gethostbyname(dns_params.hostname))) { + write(child_out, &h_errno, sizeof(int)); + close(child_out); + if (show_debug) + printf("DNS Error: %d\n", h_errno); + _exit(0); + } + memset(&sin, 0, sizeof(struct sockaddr_in)); + memcpy(&sin.sin_addr.s_addr, hp->h_addr, hp->h_length); + sin.sin_family = hp->h_addrtype; + } else + sin.sin_family = AF_INET; + + sin.sin_port = htons(dns_params.port); + write(child_out, &addrlen, sizeof(addrlen)); + write(child_out, &sin, addrlen); + write(child_out, &zero, sizeof(zero)); +#endif + dns_params.hostname[0] = '\0'; + } + + close(child_out); + close(child_in); + + _exit(0); +} + +static pending_dns_request_t * +gaim_dns_new_resolverthread(gboolean show_debug) +{ + pending_dns_request_t *req; + int child_out[2], child_in[2]; + + /* Create pipes for communicating with the child process */ + if (pipe(child_out) || pipe(child_in)) { + gaim_debug_error("dns", + "Could not create pipes: %s\n", strerror(errno)); + return NULL; + } + + req = g_new(pending_dns_request_t, 1); + + cope_with_gdb_brokenness(); + + /* Fork! */ + req->dns_pid = fork(); + + /* If we are the child process... */ + if (req->dns_pid == 0) { + /* We should not access the parent's side of the pipes, so close them */ + close(child_out[0]); + close(child_in[1]); + + gaim_dns_resolverthread(child_out[1], child_in[0], show_debug); + /* The thread calls _exit() rather than returning, so we never get here */ + } + + /* We should not access the child's side of the pipes, so close them */ + close(child_out[1]); + close(child_in[0]); + if (req->dns_pid == -1) { + gaim_debug_error("dns", + "Could not create child process for DNS: %s\n", + strerror(errno)); + g_free(req); + return NULL; + } + + req->fd_out = child_out[0]; + req->fd_in = child_in[1]; + number_of_dns_children++; + gaim_debug_info("dns", + "Created new DNS child %d, there are now %d children.\n", + req->dns_pid, number_of_dns_children); + + return req; +} +/* + * End the DNS resolver child process functions. + */ + +/* + * Begin the functions for dealing with the DNS child processes. + */ +static void +req_free(pending_dns_request_t *req) +{ + g_return_if_fail(req != NULL); + + close(req->fd_in); + close(req->fd_out); + + g_free(req->host); + g_free(req); + + number_of_dns_children--; +} + +static int +send_dns_request_to_child(pending_dns_request_t *req, dns_params_t *dns_params) +{ + char ch; + int rc; + pid_t pid; + + /* This waitpid might return the child's PID if it has recently + * exited, or it might return an error if it exited "long + * enough" ago that it has already been reaped; in either + * instance, we can't use it. */ + if ((pid = waitpid (req->dns_pid, NULL, WNOHANG)) > 0) { + gaim_debug_warning("dns", + "DNS child %d no longer exists\n", req->dns_pid); + return -1; + } else if (pid < 0) { + gaim_debug_warning("dns", + "Wait for DNS child %d failed: %s\n", + req->dns_pid, strerror(errno)); + return -1; + } + + /* Let's contact this lost child! */ + rc = write(req->fd_in, dns_params, sizeof(*dns_params)); + if (rc < 0) { + gaim_debug_error("dns", + "Unable to write to DNS child %d: %d\n", + req->dns_pid, strerror(errno)); + close(req->fd_in); + return -1; + } + + g_return_val_if_fail(rc == sizeof(*dns_params), -1); + + /* Did you hear me? (This avoids some race conditions) */ + rc = read(req->fd_out, &ch, sizeof(ch)); + if (rc != 1 || ch != 'Y') + { + gaim_debug_warning("dns", + "DNS child %d not responding. Killing it!\n", + req->dns_pid); + kill(req->dns_pid, SIGKILL); + return -1; + } + + gaim_debug_info("dns", + "Successfully sent DNS request to child %d\n", req->dns_pid); + + return 0; +} + +static void +host_resolved(gpointer data, gint source, GaimInputCondition cond); + +static void +release_dns_child(pending_dns_request_t *req) +{ + g_free(req->host); + req->host = NULL; + + if (queued_requests && !g_queue_is_empty(queued_requests)) { + queued_dns_request_t *r = g_queue_pop_head(queued_requests); + req->host = g_strdup(r->params.hostname); + req->port = r->params.port; + req->callback = r->callback; + req->data = r->data; + + gaim_debug_info("dns", + "Processing queued DNS query for '%s' with child %d\n", + req->host, req->dns_pid); + + if (send_dns_request_to_child(req, &(r->params)) != 0) { + req_free(req); + req = NULL; + + gaim_debug_warning("dns", + "Intent of process queued query of '%s' failed, " + "requeueing...\n", r->params.hostname); + g_queue_push_head(queued_requests, r); + } else { + req->inpa = gaim_input_add(req->fd_out, GAIM_INPUT_READ, host_resolved, req); + g_free(r); + } + + } else { + req->host = NULL; + req->callback = NULL; + req->data = NULL; + free_dns_children = g_slist_append(free_dns_children, req); + } +} + +static void +host_resolved(gpointer data, gint source, GaimInputCondition cond) +{ + pending_dns_request_t *req = (pending_dns_request_t*)data; + int rc, err; + GSList *hosts = NULL; + struct sockaddr *addr = NULL; + size_t addrlen; + + gaim_debug_info("dns", "Got response for '%s'\n", req->host); + gaim_input_remove(req->inpa); + + rc = read(req->fd_out, &err, sizeof(err)); + if ((rc == 4) && (err != 0)) + { + char message[1024]; +#ifdef HAVE_GETADDRINFO + g_snprintf(message, sizeof(message), "DNS error: %s (pid=%d)", + gai_strerror(err), req->dns_pid); +#else + g_snprintf(message, sizeof(message), "DNS error: %d (pid=%d)", + err, req->dns_pid); +#endif + gaim_debug_error("dns", "%s\n", message); + req->callback(NULL, req->data, message); + release_dns_child(req); + return; + } + if (rc > 0) + { + while (rc > 0) { + rc = read(req->fd_out, &addrlen, sizeof(addrlen)); + if (rc > 0 && addrlen > 0) { + addr = g_malloc(addrlen); + rc = read(req->fd_out, addr, addrlen); + hosts = g_slist_append(hosts, GINT_TO_POINTER(addrlen)); + hosts = g_slist_append(hosts, addr); + } else { + break; + } + } + } else if (rc == -1) { + char message[1024]; + g_snprintf(message, sizeof(message), "Error reading from DNS child: %s",strerror(errno)); + gaim_debug_error("dns", "%s\n", message); + req->callback(NULL, req->data, message); + req_free(req); + return; + } else if (rc == 0) { + char message[1024]; + g_snprintf(message, sizeof(message), "EOF reading from DNS child"); + close(req->fd_out); + gaim_debug_error("dns", "%s\n", message); + req->callback(NULL, req->data, message); + req_free(req); + return; + } + +/* wait4(req->dns_pid, NULL, WNOHANG, NULL); */ + + req->callback(hosts, req->data, NULL); + + release_dns_child(req); +} +/* + * End the functions for dealing with the DNS child processes. + */ + +GaimDnsQueryData * +gaim_dnsquery_a(const char *hostname, int port, GaimDnsQueryConnectFunction callback, gpointer data) +{ + pending_dns_request_t *req = NULL; + dns_params_t dns_params; + gchar *host_temp; + gboolean show_debug; + + show_debug = gaim_debug_is_enabled(); + + host_temp = g_strstrip(g_strdup(hostname)); + strncpy(dns_params.hostname, host_temp, sizeof(dns_params.hostname) - 1); + g_free(host_temp); + dns_params.hostname[sizeof(dns_params.hostname) - 1] = '\0'; + dns_params.port = port; + + /* + * If we have any children, attempt to have them perform the DNS + * query. If we're able to send the query to a child, then req + * will be set to the pending_dns_request_t. Otherwise, req will + * be NULL and we'll need to create a new DNS request child. + */ + while (free_dns_children != NULL) { + req = free_dns_children->data; + free_dns_children = g_slist_remove(free_dns_children, req); + + if (send_dns_request_to_child(req, &dns_params) == 0) + /* We found an acceptable child, yay */ + break; + + req_free(req); + req = NULL; + } + + /* We need to create a new DNS request child */ + if (req == NULL) { + if (number_of_dns_children >= MAX_DNS_CHILDREN) { + queued_dns_request_t *r = g_new(queued_dns_request_t, 1); + memcpy(&(r->params), &dns_params, sizeof(dns_params)); + r->callback = callback; + r->data = data; + if (!queued_requests) + queued_requests = g_queue_new(); + g_queue_push_tail(queued_requests, r); + + gaim_debug_info("dns", + "DNS query for '%s' queued\n", dns_params.hostname); + + return (GaimDnsQueryData *)1; + } + + req = gaim_dns_new_resolverthread(show_debug); + if (req == NULL) + { + gaim_debug_error("proxy", "oh dear, this is going to explode, I give up\n"); + return NULL; + } + send_dns_request_to_child(req, &dns_params); + } + + req->host = g_strdup(hostname); + req->port = port; + req->callback = callback; + req->data = data; + req->inpa = gaim_input_add(req->fd_out, GAIM_INPUT_READ, host_resolved, req); + + return (GaimDnsQueryData *)1; +} + +#elif defined _WIN32 /* end __unix__ || __APPLE__ */ + +typedef struct _dns_tdata { + char *hostname; + int port; + GaimDnsQueryConnectFunction callback; + gpointer data; + GSList *hosts; + char *errmsg; +} dns_tdata; + +static gboolean dns_main_thread_cb(gpointer data) { + dns_tdata *td = (dns_tdata*)data; + if (td->errmsg != NULL) { + gaim_debug_info("dns", "%s\n", td->errmsg); + } + td->callback(td->hosts, td->data, td->errmsg); + g_free(td->hostname); + g_free(td->errmsg); + g_free(td); + return FALSE; +} + +static gpointer dns_thread(gpointer data) { + +#ifdef HAVE_GETADDRINFO + int rc; + struct addrinfo hints, *res, *tmp; + char servname[20]; +#else + struct sockaddr_in sin; + struct hostent *hp; +#endif + dns_tdata *td = (dns_tdata*)data; + +#ifdef HAVE_GETADDRINFO + g_snprintf(servname, sizeof(servname), "%d", td->port); + memset(&hints,0,sizeof(hints)); + + /* This is only used to convert a service + * name to a port number. As we know we are + * passing a number already, we know this + * value will not be really used by the C + * library. + */ + hints.ai_socktype = SOCK_STREAM; + if ((rc = getaddrinfo(td->hostname, servname, &hints, &res)) == 0) { + tmp = res; + while(res) { + td->hosts = g_slist_append(td->hosts, + GSIZE_TO_POINTER(res->ai_addrlen)); + td->hosts = g_slist_append(td->hosts, + g_memdup(res->ai_addr, res->ai_addrlen)); + res = res->ai_next; + } + freeaddrinfo(tmp); + } else { + td->errmsg = g_strdup_printf("DNS getaddrinfo(\"%s\", \"%s\") error: %d", td->hostname, servname, rc); + } +#else + if ((hp = gethostbyname(td->hostname))) { + memset(&sin, 0, sizeof(struct sockaddr_in)); + memcpy(&sin.sin_addr.s_addr, hp->h_addr, hp->h_length); + sin.sin_family = hp->h_addrtype; + sin.sin_port = htons(td->port); + + td->hosts = g_slist_append(td->hosts, + GSIZE_TO_POINTER(sizeof(sin))); + td->hosts = g_slist_append(td->hosts, + g_memdup(&sin, sizeof(sin))); + } else { + td->errmsg = g_strdup_printf("DNS gethostbyname(\"%s\") error: %d", td->hostname, h_errno); + } +#endif + /* back to main thread */ + g_idle_add(dns_main_thread_cb, td); + return 0; +} + +GaimDnsQueryData * +gaim_dnsquery_a(const char *hostname, int port, + GaimDnsQueryConnectFunction callback, gpointer data) +{ + dns_tdata *td; + struct sockaddr_in sin; + GError* err = NULL; + + if(inet_aton(hostname, &sin.sin_addr)) { + GSList *hosts = NULL; + sin.sin_family = AF_INET; + sin.sin_port = htons(port); + hosts = g_slist_append(hosts, GINT_TO_POINTER(sizeof(sin))); + hosts = g_slist_append(hosts, g_memdup(&sin, sizeof(sin))); + callback(hosts, data, NULL); + return (GaimDnsQueryData *)1; + } + + gaim_debug_info("dns", "DNS Lookup for: %s\n", hostname); + td = g_new0(dns_tdata, 1); + td->hostname = g_strdup(hostname); + td->port = port; + td->callback = callback; + td->data = data; + + if(!g_thread_create(dns_thread, td, FALSE, &err)) { + gaim_debug_error("dns", "DNS thread create failure: %s\n", err?err->message:""); + g_error_free(err); + g_free(td->hostname); + g_free(td); + return NULL; + } + return (GaimDnsQueryData *)1; +} + +#else /* not __unix__ or __APPLE__ or _WIN32 */ + +typedef struct { + gpointer data; + size_t addrlen; + struct sockaddr *addr; + GaimDnsQueryConnectFunction callback; +} pending_dns_request_t; + +static gboolean host_resolved(gpointer data) +{ + pending_dns_request_t *req = (pending_dns_request_t*)data; + GSList *hosts = NULL; + hosts = g_slist_append(hosts, GINT_TO_POINTER(req->addrlen)); + hosts = g_slist_append(hosts, req->addr); + req->callback(hosts, req->data, NULL); + g_free(req); + return FALSE; +} + +GaimDnsQueryData * +gaim_dnsquery_a(const char *hostname, int port, + GaimDnsQueryConnectFunction callback, gpointer data) +{ + struct sockaddr_in sin; + pending_dns_request_t *req; + + if (!inet_aton(hostname, &sin.sin_addr)) { + struct hostent *hp; + if(!(hp = gethostbyname(hostname))) { + gaim_debug_error("dns", + "gaim_gethostbyname(\"%s\", %d) failed: %d\n", + hostname, port, h_errno); + return NULL; + } + memset(&sin, 0, sizeof(struct sockaddr_in)); + memcpy(&sin.sin_addr.s_addr, hp->h_addr, hp->h_length); + sin.sin_family = hp->h_addrtype; + } else + sin.sin_family = AF_INET; + sin.sin_port = htons(port); + + req = g_new(pending_dns_request_t, 1); + req->addr = (struct sockaddr*) g_memdup(&sin, sizeof(sin)); + req->addrlen = sizeof(sin); + req->data = data; + req->callback = callback; + gaim_timeout_add(10, host_resolved, req); + return (GaimDnsQueryData *)1; +} + +#endif /* not __unix__ or __APPLE__ or _WIN32 */ diff -r d10dda2777a9 -r b63ebf84c42b core/dnsquery.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/dnsquery.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,71 @@ +/** + * @file dnsquery.h DNS query API + * @ingroup core + * + * 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 + */ +#ifndef _GAIM_DNSQUERY_H_ +#define _GAIM_DNSQUERY_H_ + +#include +#include "eventloop.h" + +typedef struct _GaimDnsQueryData GaimDnsQueryData; + +/** + * The "hosts" parameter is a linked list containing pairs of + * one size_t addrlen and one struct sockaddr *addr. + */ +typedef void (*GaimDnsQueryConnectFunction)(GSList *hosts, gpointer data, const char *error_message); + + +#include "account.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/**************************************************************************/ +/** @name DNS query API */ +/**************************************************************************/ +/*@{*/ + +/** + * Do an async dns query + * + * @param hostname The hostname to resolve + * @param port A portnumber which is stored in the struct sockaddr + * @param callback Callback to call after resolving + * @param data Extra data for the callback function + * + * @return NULL if there was an error, otherwise return a reference to + * a data structure that can be used to cancel the pending + * DNS query, if needed. + */ +GaimDnsQueryData *gaim_dnsquery_a(const char *hostname, int port, GaimDnsQueryConnectFunction callback, gpointer data); + +/*@}*/ + +#ifdef __cplusplus +} +#endif + +#endif /* _GAIM_DNSQUERY_H_ */ diff -r d10dda2777a9 -r b63ebf84c42b core/dnssrv.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/dnssrv.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,358 @@ +/** + * @file dnssrv.c + * + * gaim + * + * Copyright (C) 2005 Thomas Butter + * + * 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" + +#ifndef _WIN32 +#include +#include +#ifdef HAVE_ARPA_NAMESER_COMPAT_H +#include +#endif +#ifndef T_SRV +#define T_SRV 33 +#endif +#else +#include +/* Missing from the mingw headers */ +#ifndef DNS_TYPE_SRV +# define DNS_TYPE_SRV 33 +#endif +#endif + +#include "dnssrv.h" +#include "eventloop.h" +#include "debug.h" + +#ifndef _WIN32 +typedef union { + HEADER hdr; + u_char buf[1024]; +} queryans; +#else +static DNS_STATUS WINAPI (*MyDnsQuery_UTF8) ( + PCSTR lpstrName, WORD wType, DWORD fOptions, + PIP4_ARRAY aipServers, PDNS_RECORD* ppQueryResultsSet, + PVOID* pReserved) = NULL; +static void WINAPI (*MyDnsRecordListFree) (PDNS_RECORD pRecordList, + DNS_FREE_TYPE FreeType) = NULL; +#endif + +struct resdata { + GaimSRVCallback cb; + gpointer extradata; +#ifndef _WIN32 + guint handle; +#else + char *query; + char *errmsg; + GSList *results; +#endif +}; + +static gint responsecompare(gconstpointer ar, gconstpointer br) { + GaimSrvResponse *a = (GaimSrvResponse*)ar; + GaimSrvResponse *b = (GaimSrvResponse*)br; + + if(a->pref == b->pref) { + if(a->weight == b->weight) + return 0; + if(a->weight < b->weight) + return -1; + return 1; + } + if(a->pref < b->pref) + return -1; + return 1; +} +#ifndef _WIN32 +static void resolve(int in, int out) { + GList *ret = NULL; + GaimSrvResponse *srvres; + queryans answer; + int size; + int qdcount; + int ancount; + guchar *end; + guchar *cp; + gchar name[256]; + guint16 type, dlen, pref, weight, port; + gchar query[256]; + + if(read(in, query, 256) <= 0) { + _exit(0); + } + size = res_query( query, C_IN, T_SRV, (u_char*)&answer, sizeof( answer)); + + qdcount = ntohs(answer.hdr.qdcount); + ancount = ntohs(answer.hdr.ancount); + + + cp = (guchar*)&answer + sizeof(HEADER); + end = (guchar*)&answer + size; + + /* skip over unwanted stuff */ + while (qdcount-- > 0 && cp < end) { + size = dn_expand( (unsigned char*)&answer, end, cp, name, 256); + if(size < 0) goto end; + cp += size + QFIXEDSZ; + } + + while (ancount-- > 0 && cp < end) { + size = dn_expand((unsigned char*)&answer, end, cp, name, 256); + if(size < 0) + goto end; + + cp += size; + + GETSHORT(type,cp); + + /* skip ttl and class since we already know it */ + cp += 6; + + GETSHORT(dlen,cp); + + if (type == T_SRV) { + GETSHORT(pref,cp); + + GETSHORT(weight,cp); + + GETSHORT(port,cp); + + size = dn_expand( (unsigned char*)&answer, end, cp, name, 256); + if(size < 0 ) + goto end; + + cp += size; + + srvres = g_new0(GaimSrvResponse, 1); + strcpy(srvres->hostname, name); + srvres->pref = pref; + srvres->port = port; + srvres->weight = weight; + + ret = g_list_insert_sorted(ret, srvres, responsecompare); + } else { + cp += dlen; + } + } +end: size = g_list_length(ret); + write(out, &size, sizeof(int)); + while(g_list_first(ret)) { + write(out, g_list_first(ret)->data, sizeof(GaimSrvResponse)); + g_free(g_list_first(ret)->data); + ret = g_list_remove(ret, g_list_first(ret)->data); + } + + /* Should the resolver be reused? + * There is most likely only 1 SRV queries per prpl... + */ + _exit(0); +} + +static void resolved(gpointer data, gint source, GaimInputCondition cond) { + int size; + struct resdata *rdata = (struct resdata*)data; + GaimSrvResponse *res; + GaimSrvResponse *tmp; + int i; + GaimSRVCallback cb = rdata->cb; + + read(source, &size, sizeof(int)); + gaim_debug_info("srv","found %d SRV entries\n", size); + tmp = res = g_new0(GaimSrvResponse, size); + i = size; + while(i) { + read(source, tmp++, sizeof(GaimSrvResponse)); + i--; + } + cb(res, size, rdata->extradata); + gaim_input_remove(rdata->handle); + g_free(rdata); +} + +#else /* _WIN32 */ + +/** The Jabber Server code was inspiration for parts of this. */ + +static gboolean res_main_thread_cb(gpointer data) { + GaimSrvResponse *srvres = NULL; + int size = 0; + struct resdata *rdata = data; + + if (rdata->errmsg != NULL) { + gaim_debug_error("srv", rdata->errmsg); + g_free(rdata->errmsg); + } else { + GaimSrvResponse *srvres_tmp; + GSList *lst = rdata->results; + + size = g_slist_length(rdata->results); + + srvres_tmp = srvres = g_new0(GaimSrvResponse, size); + while (lst) { + memcpy(srvres_tmp++, lst->data, sizeof(GaimSrvResponse)); + g_free(lst->data); + lst = g_slist_remove(lst, lst->data); + } + + rdata->results = lst; + } + + gaim_debug_info("srv", "found %d SRV entries\n", size); + + rdata->cb(srvres, size, rdata->extradata); + + g_free(rdata->query); + g_free(rdata); + + return FALSE; +} + +static gpointer res_thread(gpointer data) { + PDNS_RECORD dr = NULL; + int type = DNS_TYPE_SRV; + DNS_STATUS ds; + struct resdata *rdata = data; + + ds = MyDnsQuery_UTF8(rdata->query, type, DNS_QUERY_STANDARD, NULL, &dr, NULL); + if (ds != ERROR_SUCCESS) { + gchar *msg = g_win32_error_message(ds); + rdata->errmsg = g_strdup_printf("Couldn't look up SRV record. %s (%lu).\n", msg, ds); + g_free(msg); + } else { + PDNS_RECORD dr_tmp; + GSList *lst = NULL; + DNS_SRV_DATA *srv_data; + GaimSrvResponse *srvres; + + for (dr_tmp = dr; dr_tmp != NULL; dr_tmp = dr_tmp->pNext) { + /* Discard any incorrect entries. I'm not sure if this is necessary */ + if (dr_tmp->wType != type || strcmp(dr_tmp->pName, rdata->query) != 0) { + continue; + } + + srv_data = &dr_tmp->Data.SRV; + srvres = g_new0(GaimSrvResponse, 1); + strncpy(srvres->hostname, srv_data->pNameTarget, 255); + srvres->hostname[255] = '\0'; + srvres->pref = srv_data->wPriority; + srvres->port = srv_data->wPort; + srvres->weight = srv_data->wWeight; + + lst = g_slist_insert_sorted(lst, srvres, responsecompare); + } + + MyDnsRecordListFree(dr, DnsFreeRecordList); + rdata->results = lst; + } + + /* back to main thread */ + g_idle_add(res_main_thread_cb, rdata); + + g_thread_exit(NULL); + return NULL; +} + +#endif + +/* + * TODO: It would be really good if this returned some sort of handle + * that we could use to cancel the DNS query. As it is now, + * each callback has to check to make sure gc is still valid. + * And that is ugly. + */ +void gaim_srv_resolve(const char *protocol, const char *transport, const char *domain, GaimSRVCallback cb, gpointer extradata) { + char *query = g_strdup_printf("_%s._%s.%s",protocol, transport, domain); + struct resdata *rdata; +#ifndef _WIN32 + int in[2], out[2]; + int pid; + gaim_debug_info("srv","querying SRV record for %s\n", query); + if(pipe(in) || pipe(out)) { + gaim_debug_error("srv", "Could not create pipe\n"); + g_free(query); + cb(NULL, 0, extradata); + return; + } + + pid = fork(); + + if(pid == -1) { + gaim_debug_error("srv","Could not create process!\n"); + cb(NULL, 0, extradata); + g_free(query); + return; + } + /* Child */ + if( pid == 0 ) { + close(out[0]); + close(in[1]); + resolve(in[0], out[1]); + } + + close(out[1]); + close(in[0]); + + if(write(in[1], query, strlen(query)+1)<0) { + gaim_debug_error("srv", "Could not write to SRV resolver\n"); + } + rdata = g_new0(struct resdata,1); + rdata->cb = cb; + rdata->extradata = extradata; + rdata->handle = gaim_input_add(out[0], GAIM_INPUT_READ, resolved, rdata); + + g_free(query); +#else + GError* err = NULL; + + static gboolean initialized = FALSE; + + gaim_debug_info("srv","querying SRV record for %s\n", query); + + if (!initialized) { + MyDnsQuery_UTF8 = (void*) wgaim_find_and_loadproc("dnsapi.dll", "DnsQuery_UTF8"); + MyDnsRecordListFree = (void*) wgaim_find_and_loadproc( + "dnsapi.dll", "DnsRecordListFree"); + initialized = TRUE; + } + + if (!MyDnsQuery_UTF8 || !MyDnsRecordListFree) { + gaim_debug_error("srv", "System missing DNS API (Requires W2K+)\n"); + g_free(query); + cb(NULL, 0, extradata); + return; + } + + rdata = g_new0(struct resdata, 1); + rdata->cb = cb; + rdata->query = query; + rdata->extradata = extradata; + + if (!g_thread_create(res_thread, rdata, FALSE, &err)) { + rdata->errmsg = g_strdup_printf("SRV thread create failure: %s\n", err ? err->message : ""); + g_error_free(err); + res_main_thread_cb(rdata); + } +#endif +} + diff -r d10dda2777a9 -r b63ebf84c42b core/dnssrv.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/dnssrv.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,48 @@ +/** + * @file dnssrv.h + * + * gaim + * + * Copyright (C) 2005, Thomas Butter + * + * 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_DNSSRV_H +#define _GAIM_DNSSRV_H + +typedef struct _GaimSrvResponse GaimSrvResponse; + +struct _GaimSrvResponse { + char hostname[256]; + int port; + int weight; + int pref; +}; + +typedef void (*GaimSRVCallback)(GaimSrvResponse *resp, int results, gpointer data); + +/** + * Queries an SRV record. + * + * @param protocol Name of the protocol (e.g. "sip") + * @param transport Name of the transport ("tcp" or "udp") + * @param domain Domainname to query (e.g. "blubb.com") + * @param cb A callback which will be called with the results + * @param extradata Extra data to be passed to the callback + */ +void gaim_srv_resolve(const char *protocol, const char *transport, const char *domain, GaimSRVCallback cb, gpointer extradata); + +#endif /* _GAIM_DNSSRV_H */ diff -r d10dda2777a9 -r b63ebf84c42b core/eventloop.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/eventloop.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,73 @@ +/** + * @file eventloop.c Gaim Event Loop API + * @ingroup core + * + * 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 "eventloop.h" + +static GaimEventLoopUiOps *eventloop_ui_ops = NULL; + +guint +gaim_timeout_add(guint interval, GSourceFunc function, gpointer data) +{ + GaimEventLoopUiOps *ops = gaim_eventloop_get_ui_ops(); + + return ops->timeout_add(interval, function, data); +} + +guint +gaim_timeout_remove(guint tag) +{ + GaimEventLoopUiOps *ops = gaim_eventloop_get_ui_ops(); + + return ops->timeout_remove(tag); +} + +guint +gaim_input_add(int source, GaimInputCondition condition, GaimInputFunction func, gpointer user_data) +{ + GaimEventLoopUiOps *ops = gaim_eventloop_get_ui_ops(); + + return ops->input_add(source, condition, func, user_data); +} + +guint +gaim_input_remove(guint tag) +{ + GaimEventLoopUiOps *ops = gaim_eventloop_get_ui_ops(); + + return ops->input_remove(tag); +} + +void +gaim_eventloop_set_ui_ops(GaimEventLoopUiOps *ops) +{ + eventloop_ui_ops = ops; +} + +GaimEventLoopUiOps * +gaim_eventloop_get_ui_ops(void) +{ + g_return_val_if_fail(eventloop_ui_ops != NULL, NULL); + + return eventloop_ui_ops; +} diff -r d10dda2777a9 -r b63ebf84c42b core/eventloop.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/eventloop.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,151 @@ +/** + * @file eventloop.h Gaim Event Loop API + * @ingroup core + * + * 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 + */ +#ifndef _GAIM_EVENTLOOP_H_ +#define _GAIM_EVENTLOOP_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * An input condition. + */ +typedef enum +{ + GAIM_INPUT_READ = 1 << 0, /**< A read condition. */ + GAIM_INPUT_WRITE = 1 << 1 /**< A write condition. */ + +} GaimInputCondition; + +typedef void (*GaimInputFunction)(gpointer, gint, GaimInputCondition); + +typedef struct _GaimEventLoopUiOps GaimEventLoopUiOps; + +struct _GaimEventLoopUiOps +{ + /** + * Creates a callback timer. + * @see g_timeout_add, gaim_timeout_add + **/ + guint (*timeout_add)(guint interval, GSourceFunc function, gpointer data); + + /** + * Removes a callback timer. + * @see gaim_timeout_remove, g_source_remove + */ + guint (*timeout_remove)(guint handle); + + /** + * Adds an input handler. + * @see gaim_input_add, g_io_add_watch_full + */ + guint (*input_add)(int fd, GaimInputCondition cond, + GaimInputFunction func, gpointer user_data); + + /** + * Removes an input handler. + * @see gaim_input_remove, g_source_remove + */ + guint (*input_remove)(guint handle); +}; + +/**************************************************************************/ +/** @name Event Loop API */ +/**************************************************************************/ +/*@{*/ +/** + * Creates a callback timer. + * The timer will repeat until the function returns @c FALSE. The + * first call will be at the end of the first interval. + * @param interval The time between calls of the function, in + * milliseconds. + * @param function The function to call. + * @param data data to pass to @a function. + * @return A handle to the timer which can be passed to + * gaim_timeout_remove to remove the timer. + */ +guint gaim_timeout_add(guint interval, GSourceFunc function, gpointer data); + +/** + * Removes a timeout handler. + * + * @param handle The handle, as returned by gaim_timeout_add. + * + * @return Something. + */ +guint gaim_timeout_remove(guint handle); + +/** + * Adds an input handler. + * + * @param fd The input file descriptor. + * @param cond The condition type. + * @param func The callback function for data. + * @param user_data User-specified data. + * + * @return The resulting handle (will be greater than 0). + * @see g_io_add_watch_full + */ +guint gaim_input_add(int fd, GaimInputCondition cond, + GaimInputFunction func, gpointer user_data); + +/** + * Removes an input handler. + * + * @param handle The handle of the input handler. Note that this is the return + * value from gaim_input_add, not the file descriptor. + */ +guint gaim_input_remove(guint handle); + +/*@}*/ + + +/**************************************************************************/ +/** @name UI Registration Functions */ +/**************************************************************************/ +/*@{*/ +/** + * Sets the UI operations structure to be used for accounts. + * + * @param ops The UI operations structure. + */ +void gaim_eventloop_set_ui_ops(GaimEventLoopUiOps *ops); + +/** + * Returns the UI operations structure used for accounts. + * + * @return The UI operations structure in use. + */ +GaimEventLoopUiOps *gaim_eventloop_get_ui_ops(void); + +/*@}*/ + +#ifdef __cplusplus +} +#endif + +#endif /* _GAIM_EVENTLOOP_H_ */ diff -r d10dda2777a9 -r b63ebf84c42b core/ft.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/ft.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,1236 @@ +/** + * @file ft.c File Transfer API + * + * 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 "ft.h" +#include "network.h" +#include "notify.h" +#include "prefs.h" +#include "proxy.h" +#include "request.h" +#include "util.h" + +static GaimXferUiOps *xfer_ui_ops = NULL; + +GaimXfer * +gaim_xfer_new(GaimAccount *account, GaimXferType type, const char *who) +{ + GaimXfer *xfer; + GaimXferUiOps *ui_ops; + + g_return_val_if_fail(type != GAIM_XFER_UNKNOWN, NULL); + g_return_val_if_fail(account != NULL, NULL); + g_return_val_if_fail(who != NULL, NULL); + + xfer = g_new0(GaimXfer, 1); + + xfer->ref = 1; + xfer->type = type; + xfer->account = account; + xfer->who = g_strdup(who); + xfer->ui_ops = gaim_xfers_get_ui_ops(); + xfer->message = NULL; + + ui_ops = gaim_xfer_get_ui_ops(xfer); + + if (ui_ops != NULL && ui_ops->new_xfer != NULL) + ui_ops->new_xfer(xfer); + + return xfer; +} + +static void +gaim_xfer_destroy(GaimXfer *xfer) +{ + GaimXferUiOps *ui_ops; + + g_return_if_fail(xfer != NULL); + + /* Close the file browser, if it's open */ + gaim_request_close_with_handle(xfer); + + if (gaim_xfer_get_status(xfer) == GAIM_XFER_STATUS_STARTED) + gaim_xfer_cancel_local(xfer); + + ui_ops = gaim_xfer_get_ui_ops(xfer); + + if (ui_ops != NULL && ui_ops->destroy != NULL) + ui_ops->destroy(xfer); + + g_free(xfer->who); + g_free(xfer->filename); + g_free(xfer->remote_ip); + g_free(xfer->local_filename); + + g_free(xfer); +} + +void +gaim_xfer_ref(GaimXfer *xfer) +{ + g_return_if_fail(xfer != NULL); + + xfer->ref++; +} + +void +gaim_xfer_unref(GaimXfer *xfer) +{ + g_return_if_fail(xfer != NULL); + g_return_if_fail(xfer->ref > 0); + + xfer->ref--; + + if (xfer->ref == 0) + gaim_xfer_destroy(xfer); +} + +static void +gaim_xfer_set_status(GaimXfer *xfer, GaimXferStatusType status) +{ + g_return_if_fail(xfer != NULL); + + if(xfer->type == GAIM_XFER_SEND) { + switch(status) { + case GAIM_XFER_STATUS_ACCEPTED: + gaim_signal_emit(gaim_xfers_get_handle(), "file-send-accept", xfer); + break; + case GAIM_XFER_STATUS_STARTED: + gaim_signal_emit(gaim_xfers_get_handle(), "file-send-start", xfer); + break; + case GAIM_XFER_STATUS_DONE: + gaim_signal_emit(gaim_xfers_get_handle(), "file-send-complete", xfer); + break; + case GAIM_XFER_STATUS_CANCEL_LOCAL: + case GAIM_XFER_STATUS_CANCEL_REMOTE: + gaim_signal_emit(gaim_xfers_get_handle(), "file-send-cancel", xfer); + break; + default: + break; + } + } else if(xfer->type == GAIM_XFER_RECEIVE) { + switch(status) { + case GAIM_XFER_STATUS_ACCEPTED: + gaim_signal_emit(gaim_xfers_get_handle(), "file-recv-accept", xfer); + break; + case GAIM_XFER_STATUS_STARTED: + gaim_signal_emit(gaim_xfers_get_handle(), "file-recv-start", xfer); + break; + case GAIM_XFER_STATUS_DONE: + gaim_signal_emit(gaim_xfers_get_handle(), "file-recv-complete", xfer); + break; + case GAIM_XFER_STATUS_CANCEL_LOCAL: + case GAIM_XFER_STATUS_CANCEL_REMOTE: + gaim_signal_emit(gaim_xfers_get_handle(), "file-recv-cancel", xfer); + break; + default: + break; + } + } + + xfer->status = status; +} + +void gaim_xfer_conversation_write(GaimXfer *xfer, char *message, gboolean is_error) +{ + GaimConversation *conv = NULL; + GaimMessageFlags flags = GAIM_MESSAGE_SYSTEM; + char *escaped; + + g_return_if_fail(xfer != NULL); + g_return_if_fail(message != NULL); + + conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM, xfer->who, + gaim_xfer_get_account(xfer)); + + if (conv == NULL) + return; + + escaped = g_markup_escape_text(message, -1); + + if (is_error) + flags = GAIM_MESSAGE_ERROR; + + gaim_conversation_write(conv, NULL, escaped, flags, time(NULL)); + g_free(escaped); +} + +static void gaim_xfer_show_file_error(GaimXfer *xfer, const char *filename) +{ + int err = errno; + gchar *msg = NULL, *utf8; + GaimXferType xfer_type = gaim_xfer_get_type(xfer); + GaimAccount *account = gaim_xfer_get_account(xfer); + + utf8 = g_filename_to_utf8(filename, -1, NULL, NULL, NULL); + switch(xfer_type) { + case GAIM_XFER_SEND: + msg = g_strdup_printf(_("Error reading %s: \n%s.\n"), + utf8, strerror(err)); + break; + case GAIM_XFER_RECEIVE: + msg = g_strdup_printf(_("Error writing %s: \n%s.\n"), + utf8, strerror(err)); + break; + default: + msg = g_strdup_printf(_("Error accessing %s: \n%s.\n"), + utf8, strerror(err)); + break; + } + g_free(utf8); + + gaim_xfer_conversation_write(xfer, msg, TRUE); + gaim_xfer_error(xfer_type, account, xfer->who, msg); + g_free(msg); +} + +static void +gaim_xfer_choose_file_ok_cb(void *user_data, const char *filename) +{ + GaimXfer *xfer; + struct stat st; + + xfer = (GaimXfer *)user_data; + + if (g_stat(filename, &st) != 0) { + /* File not found. */ + if (gaim_xfer_get_type(xfer) == GAIM_XFER_RECEIVE) { + gaim_xfer_request_accepted(xfer, filename); + } + else { + gaim_xfer_show_file_error(xfer, filename); + gaim_xfer_request_denied(xfer); + } + } + else if ((gaim_xfer_get_type(xfer) == GAIM_XFER_SEND) && + (st.st_size == 0)) { + + gaim_notify_error(NULL, NULL, + _("Cannot send a file of 0 bytes."), NULL); + + gaim_xfer_request_denied(xfer); + } + else if ((gaim_xfer_get_type(xfer) == GAIM_XFER_SEND) && + S_ISDIR(st.st_mode)) { + /* + * XXX - Sending a directory should be valid for some protocols. + */ + gaim_notify_error(NULL, NULL, + _("Cannot send a directory."), NULL); + + gaim_xfer_request_denied(xfer); + } + else if ((gaim_xfer_get_type(xfer) == GAIM_XFER_RECEIVE) && + S_ISDIR(st.st_mode)) { + char *msg, *utf8; + utf8 = g_filename_to_utf8(filename, -1, NULL, NULL, NULL); + msg = g_strdup_printf( + _("%s is not a regular file. Cowardly refusing to overwrite it.\n"), utf8); + g_free(utf8); + gaim_notify_error(NULL, NULL, msg, NULL); + g_free(msg); + gaim_xfer_request_denied(xfer); + } + else { + gaim_xfer_request_accepted(xfer, filename); + } + + gaim_xfer_unref(xfer); +} + +static void +gaim_xfer_choose_file_cancel_cb(void *user_data, const char *filename) +{ + GaimXfer *xfer = (GaimXfer *)user_data; + + gaim_xfer_set_status(xfer, GAIM_XFER_STATUS_CANCEL_LOCAL); + gaim_xfer_request_denied(xfer); +} + +static int +gaim_xfer_choose_file(GaimXfer *xfer) +{ + gaim_request_file(xfer, NULL, gaim_xfer_get_filename(xfer), + (gaim_xfer_get_type(xfer) == GAIM_XFER_RECEIVE), + G_CALLBACK(gaim_xfer_choose_file_ok_cb), + G_CALLBACK(gaim_xfer_choose_file_cancel_cb), xfer); + + return 0; +} + +static int +cancel_recv_cb(GaimXfer *xfer) +{ + gaim_xfer_set_status(xfer, GAIM_XFER_STATUS_CANCEL_LOCAL); + gaim_xfer_request_denied(xfer); + gaim_xfer_unref(xfer); + + return 0; +} + +static void +gaim_xfer_ask_recv(GaimXfer *xfer) +{ + char *buf, *size_buf; + size_t size; + + /* If we have already accepted the request, ask the destination file + name directly */ + if (gaim_xfer_get_status(xfer) != GAIM_XFER_STATUS_ACCEPTED) { + GaimBuddy *buddy = gaim_find_buddy(xfer->account, xfer->who); + + if (gaim_xfer_get_filename(xfer) != NULL) + { + size = gaim_xfer_get_size(xfer); + size_buf = gaim_str_size_to_units(size); + buf = g_strdup_printf(_("%s wants to send you %s (%s)"), + buddy ? gaim_buddy_get_alias(buddy) : xfer->who, + gaim_xfer_get_filename(xfer), size_buf); + g_free(size_buf); + } + else + { + buf = g_strdup_printf(_("%s wants to send you a file"), + buddy ? gaim_buddy_get_alias(buddy) : xfer->who); + } + + if (xfer->message != NULL) + serv_got_im(gaim_account_get_connection(xfer->account), + xfer->who, xfer->message, 0, time(NULL)); + + gaim_request_accept_cancel(xfer, NULL, buf, NULL, + GAIM_DEFAULT_ACTION_NONE, xfer, + G_CALLBACK(gaim_xfer_choose_file), + G_CALLBACK(cancel_recv_cb)); + + g_free(buf); + } else + gaim_xfer_choose_file(xfer); +} + +static int +ask_accept_ok(GaimXfer *xfer) +{ + gaim_xfer_request_accepted(xfer, NULL); + + return 0; +} + +static int +ask_accept_cancel(GaimXfer *xfer) +{ + gaim_xfer_request_denied(xfer); + gaim_xfer_unref(xfer); + + return 0; +} + +static void +gaim_xfer_ask_accept(GaimXfer *xfer) +{ + char *buf, *buf2 = NULL; + GaimBuddy *buddy = gaim_find_buddy(xfer->account, xfer->who); + + buf = g_strdup_printf(_("Accept file transfer request from %s?"), + buddy ? gaim_buddy_get_alias(buddy) : xfer->who); + if (gaim_xfer_get_remote_ip(xfer) && + gaim_xfer_get_remote_port(xfer)) + buf2 = g_strdup_printf(_("A file is available for download from:\n" + "Remote host: %s\nRemote port: %d"), + gaim_xfer_get_remote_ip(xfer), + gaim_xfer_get_remote_port(xfer)); + gaim_request_accept_cancel(xfer, NULL, buf, buf2, + GAIM_DEFAULT_ACTION_NONE, xfer, + G_CALLBACK(ask_accept_ok), + G_CALLBACK(ask_accept_cancel)); + g_free(buf); + g_free(buf2); +} + +void +gaim_xfer_request(GaimXfer *xfer) +{ + g_return_if_fail(xfer != NULL); + g_return_if_fail(xfer->ops.init != NULL); + + gaim_xfer_ref(xfer); + + if (gaim_xfer_get_type(xfer) == GAIM_XFER_RECEIVE) + { + gaim_signal_emit(gaim_xfers_get_handle(), "file-recv-request", xfer); + if (gaim_xfer_get_status(xfer) == GAIM_XFER_STATUS_CANCEL_LOCAL) + { + /* The file-transfer was cancelled by a plugin */ + gaim_xfer_cancel_local(xfer); + } + else if (gaim_xfer_get_filename(xfer) || + gaim_xfer_get_status(xfer) == GAIM_XFER_STATUS_ACCEPTED) + { + gchar* message = NULL; + GaimBuddy *buddy = gaim_find_buddy(xfer->account, xfer->who); + message = g_strdup_printf(_("%s is offering to send file %s"), + buddy ? gaim_buddy_get_alias(buddy) : xfer->who, gaim_xfer_get_filename(xfer)); + gaim_xfer_conversation_write(xfer, message, FALSE); + g_free(message); + /* Ask for a filename to save to if it's not already given by a plugin */ + if (xfer->local_filename == NULL) + gaim_xfer_ask_recv(xfer); + } + else + { + gaim_xfer_ask_accept(xfer); + } + } + else + { + gaim_xfer_choose_file(xfer); + } +} + +void +gaim_xfer_request_accepted(GaimXfer *xfer, const char *filename) +{ + GaimXferType type; + struct stat st; + char *msg, *utf8; + GaimAccount *account; + GaimBuddy *buddy; + + if (xfer == NULL) + return; + + type = gaim_xfer_get_type(xfer); + account = gaim_xfer_get_account(xfer); + + if (!filename && type == GAIM_XFER_RECEIVE) { + xfer->status = GAIM_XFER_STATUS_ACCEPTED; + xfer->ops.init(xfer); + return; + } + + buddy = gaim_find_buddy(account, xfer->who); + + if (type == GAIM_XFER_SEND) { + /* Sending a file */ + /* Check the filename. */ +#ifdef _WIN32 + if (g_strrstr(filename, "../") || g_strrstr(filename, "..\\")) { +#else + if (g_strrstr(filename, "../")) { +#endif + char *utf8 = g_filename_to_utf8(filename, -1, NULL, NULL, NULL); + + msg = g_strdup_printf(_("%s is not a valid filename.\n"), utf8); + gaim_xfer_error(type, account, xfer->who, msg); + g_free(utf8); + g_free(msg); + + gaim_xfer_unref(xfer); + return; + } + + if (g_stat(filename, &st) == -1) { + gaim_xfer_show_file_error(xfer, filename); + gaim_xfer_unref(xfer); + return; + } + + gaim_xfer_set_local_filename(xfer, filename); + gaim_xfer_set_size(xfer, st.st_size); + + utf8 = g_filename_to_utf8(g_basename(filename), -1, NULL, NULL, NULL); + gaim_xfer_set_filename(xfer, utf8); + + msg = g_strdup_printf(_("Offering to send %s to %s"), + utf8, buddy ? gaim_buddy_get_alias(buddy) : xfer->who); + g_free(utf8); + + gaim_xfer_conversation_write(xfer, msg, FALSE); + g_free(msg); + } + else { + /* Receiving a file */ + xfer->status = GAIM_XFER_STATUS_ACCEPTED; + gaim_xfer_set_local_filename(xfer, filename); + + msg = g_strdup_printf(_("Starting transfer of %s from %s"), + xfer->filename, buddy ? gaim_buddy_get_alias(buddy) : xfer->who); + gaim_xfer_conversation_write(xfer, msg, FALSE); + g_free(msg); + } + + gaim_xfer_add(xfer); + xfer->ops.init(xfer); + +} + +void +gaim_xfer_request_denied(GaimXfer *xfer) +{ + g_return_if_fail(xfer != NULL); + + if (xfer->ops.request_denied != NULL) + xfer->ops.request_denied(xfer); + + gaim_xfer_unref(xfer); +} + +GaimXferType +gaim_xfer_get_type(const GaimXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, GAIM_XFER_UNKNOWN); + + return xfer->type; +} + +GaimAccount * +gaim_xfer_get_account(const GaimXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, NULL); + + return xfer->account; +} + +GaimXferStatusType +gaim_xfer_get_status(const GaimXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, GAIM_XFER_STATUS_UNKNOWN); + + return xfer->status; +} + +gboolean +gaim_xfer_is_canceled(const GaimXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, TRUE); + + if ((gaim_xfer_get_status(xfer) == GAIM_XFER_STATUS_CANCEL_LOCAL) || + (gaim_xfer_get_status(xfer) == GAIM_XFER_STATUS_CANCEL_REMOTE)) + return TRUE; + else + return FALSE; +} + +gboolean +gaim_xfer_is_completed(const GaimXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, TRUE); + + return (gaim_xfer_get_status(xfer) == GAIM_XFER_STATUS_DONE); +} + +const char * +gaim_xfer_get_filename(const GaimXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, NULL); + + return xfer->filename; +} + +const char * +gaim_xfer_get_local_filename(const GaimXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, NULL); + + return xfer->local_filename; +} + +size_t +gaim_xfer_get_bytes_sent(const GaimXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, 0); + + return xfer->bytes_sent; +} + +size_t +gaim_xfer_get_bytes_remaining(const GaimXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, 0); + + return xfer->bytes_remaining; +} + +size_t +gaim_xfer_get_size(const GaimXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, 0); + + return xfer->size; +} + +double +gaim_xfer_get_progress(const GaimXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, 0.0); + + if (gaim_xfer_get_size(xfer) == 0) + return 0.0; + + return ((double)gaim_xfer_get_bytes_sent(xfer) / + (double)gaim_xfer_get_size(xfer)); +} + +unsigned int +gaim_xfer_get_local_port(const GaimXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, -1); + + return xfer->local_port; +} + +const char * +gaim_xfer_get_remote_ip(const GaimXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, NULL); + + return xfer->remote_ip; +} + +unsigned int +gaim_xfer_get_remote_port(const GaimXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, -1); + + return xfer->remote_port; +} + +void +gaim_xfer_set_completed(GaimXfer *xfer, gboolean completed) +{ + GaimXferUiOps *ui_ops; + + g_return_if_fail(xfer != NULL); + + if (completed == TRUE) { + char *msg = NULL; + gaim_xfer_set_status(xfer, GAIM_XFER_STATUS_DONE); + + if (gaim_xfer_get_filename(xfer) != NULL) + msg = g_strdup_printf(_("Transfer of file %s complete"), + gaim_xfer_get_filename(xfer)); + else + msg = g_strdup_printf(_("File transfer complete")); + gaim_xfer_conversation_write(xfer, msg, FALSE); + g_free(msg); + } + + ui_ops = gaim_xfer_get_ui_ops(xfer); + + if (ui_ops != NULL && ui_ops->update_progress != NULL) + ui_ops->update_progress(xfer, gaim_xfer_get_progress(xfer)); +} + +void +gaim_xfer_set_message(GaimXfer *xfer, const char *message) +{ + g_return_if_fail(xfer != NULL); + + g_free(xfer->message); + xfer->message = g_strdup(message); +} + +void +gaim_xfer_set_filename(GaimXfer *xfer, const char *filename) +{ + g_return_if_fail(xfer != NULL); + + g_free(xfer->filename); + xfer->filename = g_strdup(filename); +} + +void +gaim_xfer_set_local_filename(GaimXfer *xfer, const char *filename) +{ + g_return_if_fail(xfer != NULL); + + g_free(xfer->local_filename); + xfer->local_filename = g_strdup(filename); +} + +void +gaim_xfer_set_size(GaimXfer *xfer, size_t size) +{ + g_return_if_fail(xfer != NULL); + + if (xfer->size == 0) + xfer->bytes_remaining = size - xfer->bytes_sent; + + xfer->size = size; +} + +GaimXferUiOps * +gaim_xfer_get_ui_ops(const GaimXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, NULL); + + return xfer->ui_ops; +} + +void +gaim_xfer_set_init_fnc(GaimXfer *xfer, void (*fnc)(GaimXfer *)) +{ + g_return_if_fail(xfer != NULL); + + xfer->ops.init = fnc; +} + +void gaim_xfer_set_request_denied_fnc(GaimXfer *xfer, void (*fnc)(GaimXfer *)) +{ + g_return_if_fail(xfer != NULL); + + xfer->ops.request_denied = fnc; +} + +void +gaim_xfer_set_read_fnc(GaimXfer *xfer, gssize (*fnc)(guchar **, GaimXfer *)) +{ + g_return_if_fail(xfer != NULL); + + xfer->ops.read = fnc; +} + +void +gaim_xfer_set_write_fnc(GaimXfer *xfer, + gssize (*fnc)(const guchar *, size_t, GaimXfer *)) +{ + g_return_if_fail(xfer != NULL); + + xfer->ops.write = fnc; +} + +void +gaim_xfer_set_ack_fnc(GaimXfer *xfer, + void (*fnc)(GaimXfer *, const guchar *, size_t)) +{ + g_return_if_fail(xfer != NULL); + + xfer->ops.ack = fnc; +} + +void +gaim_xfer_set_start_fnc(GaimXfer *xfer, void (*fnc)(GaimXfer *)) +{ + g_return_if_fail(xfer != NULL); + + xfer->ops.start = fnc; +} + +void +gaim_xfer_set_end_fnc(GaimXfer *xfer, void (*fnc)(GaimXfer *)) +{ + g_return_if_fail(xfer != NULL); + + xfer->ops.end = fnc; +} + +void +gaim_xfer_set_cancel_send_fnc(GaimXfer *xfer, void (*fnc)(GaimXfer *)) +{ + g_return_if_fail(xfer != NULL); + + xfer->ops.cancel_send = fnc; +} + +void +gaim_xfer_set_cancel_recv_fnc(GaimXfer *xfer, void (*fnc)(GaimXfer *)) +{ + g_return_if_fail(xfer != NULL); + + xfer->ops.cancel_recv = fnc; +} + +gssize +gaim_xfer_read(GaimXfer *xfer, guchar **buffer) +{ + gssize s, r; + + g_return_val_if_fail(xfer != NULL, 0); + g_return_val_if_fail(buffer != NULL, 0); + + if (gaim_xfer_get_size(xfer) == 0) + s = 4096; + else + s = MIN(gaim_xfer_get_bytes_remaining(xfer), 4096); + + if (xfer->ops.read != NULL) + r = (xfer->ops.read)(buffer, xfer); + else { + *buffer = g_malloc0(s); + + r = read(xfer->fd, *buffer, s); + if (r < 0 && errno == EAGAIN) + r = 0; + else if (r < 0) + r = -1; + else if ((gaim_xfer_get_size(xfer) > 0) && + ((gaim_xfer_get_bytes_sent(xfer)+r) >= gaim_xfer_get_size(xfer))) + gaim_xfer_set_completed(xfer, TRUE); + else if (r == 0) + r = -1; + } + + return r; +} + +gssize +gaim_xfer_write(GaimXfer *xfer, const guchar *buffer, gsize size) +{ + gssize r, s; + + g_return_val_if_fail(xfer != NULL, 0); + g_return_val_if_fail(buffer != NULL, 0); + g_return_val_if_fail(size != 0, 0); + + s = MIN(gaim_xfer_get_bytes_remaining(xfer), size); + + if (xfer->ops.write != NULL) { + r = (xfer->ops.write)(buffer, s, xfer); + } else { + r = write(xfer->fd, buffer, s); + if (r < 0 && errno == EAGAIN) + r = 0; + if ((gaim_xfer_get_bytes_sent(xfer)+r) >= gaim_xfer_get_size(xfer)) + gaim_xfer_set_completed(xfer, TRUE); + } + + return r; +} + +static void +transfer_cb(gpointer data, gint source, GaimInputCondition condition) +{ + GaimXferUiOps *ui_ops; + GaimXfer *xfer = (GaimXfer *)data; + guchar *buffer = NULL; + gssize r = 0; + + if (condition & GAIM_INPUT_READ) { + r = gaim_xfer_read(xfer, &buffer); + if (r > 0) { + fwrite(buffer, 1, r, xfer->dest_fp); + } else if(r <= 0) { + gaim_xfer_cancel_remote(xfer); + return; + } + } + + if (condition & GAIM_INPUT_WRITE) { + size_t s = MIN(gaim_xfer_get_bytes_remaining(xfer), 4096); + + /* this is so the prpl can keep the connection open + if it needs to for some odd reason. */ + if (s == 0) { + if (xfer->watcher) { + gaim_input_remove(xfer->watcher); + xfer->watcher = 0; + } + return; + } + + buffer = g_malloc0(s); + + fread(buffer, 1, s, xfer->dest_fp); + + /* Write as much as we're allowed to. */ + r = gaim_xfer_write(xfer, buffer, s); + + if (r == -1) { + gaim_xfer_cancel_remote(xfer); + g_free(buffer); + return; + } else if (r < s) { + /* We have to seek back in the file now. */ + fseek(xfer->dest_fp, r - s, SEEK_CUR); + } + } + + if (r > 0) { + if (gaim_xfer_get_size(xfer) > 0) + xfer->bytes_remaining -= r; + + xfer->bytes_sent += r; + + if (xfer->ops.ack != NULL) + xfer->ops.ack(xfer, buffer, r); + + g_free(buffer); + + ui_ops = gaim_xfer_get_ui_ops(xfer); + + if (ui_ops != NULL && ui_ops->update_progress != NULL) + ui_ops->update_progress(xfer, + gaim_xfer_get_progress(xfer)); + } + + if (gaim_xfer_is_completed(xfer)) + gaim_xfer_end(xfer); +} + +static void +begin_transfer(GaimXfer *xfer, GaimInputCondition cond) +{ + GaimXferType type = gaim_xfer_get_type(xfer); + + xfer->dest_fp = g_fopen(gaim_xfer_get_local_filename(xfer), + type == GAIM_XFER_RECEIVE ? "wb" : "rb"); + + if (xfer->dest_fp == NULL) { + gaim_xfer_show_file_error(xfer, gaim_xfer_get_local_filename(xfer)); + gaim_xfer_cancel_local(xfer); + return; + } + + xfer->watcher = gaim_input_add(xfer->fd, cond, transfer_cb, xfer); + + xfer->start_time = time(NULL); + + if (xfer->ops.start != NULL) + xfer->ops.start(xfer); +} + +static void +connect_cb(gpointer data, gint source, const gchar *error_message) +{ + GaimXfer *xfer = (GaimXfer *)data; + + xfer->fd = source; + + begin_transfer(xfer, GAIM_INPUT_READ); +} + +void +gaim_xfer_start(GaimXfer *xfer, int fd, const char *ip, + unsigned int port) +{ + GaimInputCondition cond; + GaimXferType type; + + g_return_if_fail(xfer != NULL); + g_return_if_fail(gaim_xfer_get_type(xfer) != GAIM_XFER_UNKNOWN); + + type = gaim_xfer_get_type(xfer); + + xfer->bytes_remaining = gaim_xfer_get_size(xfer); + xfer->bytes_sent = 0; + + gaim_xfer_set_status(xfer, GAIM_XFER_STATUS_STARTED); + + if (type == GAIM_XFER_RECEIVE) { + cond = GAIM_INPUT_READ; + + if (ip != NULL) { + xfer->remote_ip = g_strdup(ip); + xfer->remote_port = port; + + /* Establish a file descriptor. */ + gaim_proxy_connect(xfer->account, xfer->remote_ip, + xfer->remote_port, connect_cb, xfer); + + return; + } + else { + xfer->fd = fd; + } + } + else { + cond = GAIM_INPUT_WRITE; + + xfer->fd = fd; + } + + begin_transfer(xfer, cond); +} + +void +gaim_xfer_end(GaimXfer *xfer) +{ + g_return_if_fail(xfer != NULL); + + /* See if we are actually trying to cancel this. */ + if (!gaim_xfer_is_completed(xfer)) { + gaim_xfer_cancel_local(xfer); + return; + } + + xfer->end_time = time(NULL); + if (xfer->ops.end != NULL) + xfer->ops.end(xfer); + + if (xfer->watcher != 0) { + gaim_input_remove(xfer->watcher); + xfer->watcher = 0; + } + + if (xfer->fd != 0) + close(xfer->fd); + + if (xfer->dest_fp != NULL) { + fclose(xfer->dest_fp); + xfer->dest_fp = NULL; + } + + gaim_xfer_unref(xfer); +} + +void +gaim_xfer_add(GaimXfer *xfer) +{ + GaimXferUiOps *ui_ops; + + g_return_if_fail(xfer != NULL); + + ui_ops = gaim_xfer_get_ui_ops(xfer); + + if (ui_ops != NULL && ui_ops->add_xfer != NULL) + ui_ops->add_xfer(xfer); +} + +void +gaim_xfer_cancel_local(GaimXfer *xfer) +{ + GaimXferUiOps *ui_ops; + char *msg = NULL; + + g_return_if_fail(xfer != NULL); + + gaim_xfer_set_status(xfer, GAIM_XFER_STATUS_CANCEL_LOCAL); + xfer->end_time = time(NULL); + + if (gaim_xfer_get_filename(xfer) != NULL) + { + msg = g_strdup_printf(_("You canceled the transfer of %s"), + gaim_xfer_get_filename(xfer)); + } + else + { + msg = g_strdup_printf(_("File transfer cancelled")); + } + gaim_xfer_conversation_write(xfer, msg, FALSE); + g_free(msg); + + if (gaim_xfer_get_type(xfer) == GAIM_XFER_SEND) + { + if (xfer->ops.cancel_send != NULL) + xfer->ops.cancel_send(xfer); + } + else + { + if (xfer->ops.cancel_recv != NULL) + xfer->ops.cancel_recv(xfer); + } + + if (xfer->watcher != 0) { + gaim_input_remove(xfer->watcher); + xfer->watcher = 0; + } + + if (xfer->fd != 0) + close(xfer->fd); + + if (xfer->dest_fp != NULL) { + fclose(xfer->dest_fp); + xfer->dest_fp = NULL; + } + + ui_ops = gaim_xfer_get_ui_ops(xfer); + + if (ui_ops != NULL && ui_ops->cancel_local != NULL) + ui_ops->cancel_local(xfer); + + xfer->bytes_remaining = 0; + + gaim_xfer_unref(xfer); +} + +void +gaim_xfer_cancel_remote(GaimXfer *xfer) +{ + GaimXferUiOps *ui_ops; + gchar *msg; + GaimAccount *account; + GaimBuddy *buddy; + + g_return_if_fail(xfer != NULL); + + gaim_request_close_with_handle(xfer); + gaim_xfer_set_status(xfer, GAIM_XFER_STATUS_CANCEL_REMOTE); + xfer->end_time = time(NULL); + + account = gaim_xfer_get_account(xfer); + buddy = gaim_find_buddy(account, xfer->who); + + if (gaim_xfer_get_filename(xfer) != NULL) + { + msg = g_strdup_printf(_("%s canceled the transfer of %s"), + buddy ? gaim_buddy_get_alias(buddy) : xfer->who, gaim_xfer_get_filename(xfer)); + } + else + { + msg = g_strdup_printf(_("%s canceled the file transfer"), + buddy ? gaim_buddy_get_alias(buddy) : xfer->who); + } + gaim_xfer_conversation_write(xfer, msg, TRUE); + gaim_xfer_error(gaim_xfer_get_type(xfer), account, xfer->who, msg); + g_free(msg); + + if (gaim_xfer_get_type(xfer) == GAIM_XFER_SEND) + { + if (xfer->ops.cancel_send != NULL) + xfer->ops.cancel_send(xfer); + } + else + { + if (xfer->ops.cancel_recv != NULL) + xfer->ops.cancel_recv(xfer); + } + + if (xfer->watcher != 0) { + gaim_input_remove(xfer->watcher); + xfer->watcher = 0; + } + + if (xfer->fd != 0) + close(xfer->fd); + + if (xfer->dest_fp != NULL) { + fclose(xfer->dest_fp); + xfer->dest_fp = NULL; + } + + ui_ops = gaim_xfer_get_ui_ops(xfer); + + if (ui_ops != NULL && ui_ops->cancel_remote != NULL) + ui_ops->cancel_remote(xfer); + + xfer->bytes_remaining = 0; + + gaim_xfer_unref(xfer); +} + +void +gaim_xfer_error(GaimXferType type, GaimAccount *account, const char *who, const char *msg) +{ + char *title; + + g_return_if_fail(msg != NULL); + g_return_if_fail(type != GAIM_XFER_UNKNOWN); + + if (account) { + GaimBuddy *buddy; + buddy = gaim_find_buddy(account, who); + if (buddy) + who = gaim_buddy_get_alias(buddy); + } + + if (type == GAIM_XFER_SEND) + title = g_strdup_printf(_("File transfer to %s failed."), who); + else + title = g_strdup_printf(_("File transfer from %s failed."), who); + + gaim_notify_error(NULL, NULL, title, msg); + + g_free(title); +} + +void +gaim_xfer_update_progress(GaimXfer *xfer) +{ + GaimXferUiOps *ui_ops; + + g_return_if_fail(xfer != NULL); + + ui_ops = gaim_xfer_get_ui_ops(xfer); + if (ui_ops != NULL && ui_ops->update_progress != NULL) + ui_ops->update_progress(xfer, gaim_xfer_get_progress(xfer)); +} + + +/************************************************************************** + * File Transfer Subsystem API + **************************************************************************/ +void * +gaim_xfers_get_handle(void) { + static int handle = 0; + + return &handle; +} + +void +gaim_xfers_init(void) { + void *handle = gaim_xfers_get_handle(); + + /* register signals */ + gaim_signal_register(handle, "file-recv-accept", + gaim_marshal_VOID__POINTER, + NULL, 1, + gaim_value_new(GAIM_TYPE_SUBTYPE, GAIM_SUBTYPE_XFER)); + gaim_signal_register(handle, "file-send-accept", + gaim_marshal_VOID__POINTER, + NULL, 1, + gaim_value_new(GAIM_TYPE_POINTER, GAIM_SUBTYPE_XFER)); + gaim_signal_register(handle, "file-recv-start", + gaim_marshal_VOID__POINTER, + NULL, 1, + gaim_value_new(GAIM_TYPE_POINTER, GAIM_SUBTYPE_XFER)); + gaim_signal_register(handle, "file-send-start", + gaim_marshal_VOID__POINTER, + NULL, 1, + gaim_value_new(GAIM_TYPE_POINTER, GAIM_SUBTYPE_XFER)); + gaim_signal_register(handle, "file-send-cancel", + gaim_marshal_VOID__POINTER, + NULL, 1, + gaim_value_new(GAIM_TYPE_POINTER, GAIM_SUBTYPE_XFER)); + gaim_signal_register(handle, "file-recv-cancel", + gaim_marshal_VOID__POINTER, + NULL, 1, + gaim_value_new(GAIM_TYPE_POINTER, GAIM_SUBTYPE_XFER)); + gaim_signal_register(handle, "file-send-complete", + gaim_marshal_VOID__POINTER, + NULL, 1, + gaim_value_new(GAIM_TYPE_POINTER, GAIM_SUBTYPE_XFER)); + gaim_signal_register(handle, "file-recv-complete", + gaim_marshal_VOID__POINTER, + NULL, 1, + gaim_value_new(GAIM_TYPE_POINTER, GAIM_SUBTYPE_XFER)); + gaim_signal_register(handle, "file-recv-request", + gaim_marshal_VOID__POINTER, + NULL, 1, + gaim_value_new(GAIM_TYPE_POINTER, GAIM_SUBTYPE_XFER)); +} + +void +gaim_xfers_uninit(void) { + gaim_signals_disconnect_by_handle(gaim_xfers_get_handle()); +} + +void +gaim_xfers_set_ui_ops(GaimXferUiOps *ops) { + xfer_ui_ops = ops; +} + +GaimXferUiOps * +gaim_xfers_get_ui_ops(void) { + return xfer_ui_ops; +} diff -r d10dda2777a9 -r b63ebf84c42b core/ft.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/ft.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,602 @@ +/** + * @file ft.h File Transfer API + * @ingroup core + * + * 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 + */ +#ifndef _GAIM_FT_H_ +#define _GAIM_FT_H_ + +/**************************************************************************/ +/** Data Structures */ +/**************************************************************************/ +typedef struct _GaimXfer GaimXfer; + +#include +#include + +#include "account.h" + +/** + * Types of file transfers. + */ +typedef enum +{ + GAIM_XFER_UNKNOWN = 0, /**< Unknown file transfer type. */ + GAIM_XFER_SEND, /**< File sending. */ + GAIM_XFER_RECEIVE /**< File receiving. */ + +} GaimXferType; + +/** + * The different states of the xfer. + */ +typedef enum +{ + GAIM_XFER_STATUS_UNKNOWN = 0, /**< Unknown, the xfer may be null. */ + GAIM_XFER_STATUS_NOT_STARTED, /**< It hasn't started yet. */ + GAIM_XFER_STATUS_ACCEPTED, /**< Receive accepted, but destination file not selected yet */ + GAIM_XFER_STATUS_STARTED, /**< gaim_xfer_start has been called. */ + GAIM_XFER_STATUS_DONE, /**< The xfer completed successfully. */ + GAIM_XFER_STATUS_CANCEL_LOCAL, /**< The xfer was canceled by us. */ + GAIM_XFER_STATUS_CANCEL_REMOTE /**< The xfer was canceled by the other end, or we couldn't connect. */ +} GaimXferStatusType; + +/** + * File transfer UI operations. + * + * Any UI representing a file transfer must assign a filled-out + * GaimXferUiOps structure to the gaim_xfer. + */ +typedef struct +{ + void (*new_xfer)(GaimXfer *xfer); + void (*destroy)(GaimXfer *xfer); + void (*add_xfer)(GaimXfer *xfer); + void (*update_progress)(GaimXfer *xfer, double percent); + void (*cancel_local)(GaimXfer *xfer); + void (*cancel_remote)(GaimXfer *xfer); + +} GaimXferUiOps; + +/** + * A core representation of a file transfer. + */ +struct _GaimXfer +{ + guint ref; /**< The reference count. */ + GaimXferType type; /**< The type of transfer. */ + + GaimAccount *account; /**< The account. */ + + char *who; /**< The person on the other end of the + transfer. */ + + char *message; /**< A message sent with the request */ + char *filename; /**< The name sent over the network. */ + char *local_filename; /**< The name on the local hard drive. */ + size_t size; /**< The size of the file. */ + + FILE *dest_fp; /**< The destination file pointer. */ + + char *remote_ip; /**< The remote IP address. */ + int local_port; /**< The local port. */ + int remote_port; /**< The remote port. */ + + int fd; /**< The socket file descriptor. */ + int watcher; /**< Watcher. */ + + size_t bytes_sent; /**< The number of bytes sent. */ + size_t bytes_remaining; /**< The number of bytes remaining. */ + time_t start_time; /**< When the transfer of data began. */ + time_t end_time; /**< When the transfer of data ended. */ + + GaimXferStatusType status; /**< File Transfer's status. */ + + /* I/O operations. */ + struct + { + void (*init)(GaimXfer *xfer); + void (*request_denied)(GaimXfer *xfer); + void (*start)(GaimXfer *xfer); + void (*end)(GaimXfer *xfer); + void (*cancel_send)(GaimXfer *xfer); + void (*cancel_recv)(GaimXfer *xfer); + gssize (*read)(guchar **buffer, GaimXfer *xfer); + gssize (*write)(const guchar *buffer, size_t size, GaimXfer *xfer); + void (*ack)(GaimXfer *xfer, const guchar *buffer, size_t size); + + } ops; + + GaimXferUiOps *ui_ops; /**< UI-specific operations. */ + void *ui_data; /**< UI-specific data. */ + + void *data; /**< prpl-specific data. */ +}; + +#ifdef __cplusplus +extern "C" { +#endif + +/**************************************************************************/ +/** @name File Transfer API */ +/**************************************************************************/ +/*@{*/ + +/** + * Creates a new file transfer handle. + * This is called by prpls. + * The handle starts with a ref count of 1, and this reference + * is owned by the core. The prpl normally does not need to + * gaim_xfer_ref or unref. + * + * @param account The account sending or receiving the file. + * @param type The type of file transfer. + * @param who The name of the remote user. + * + * @return A file transfer handle. + */ +GaimXfer *gaim_xfer_new(GaimAccount *account, + GaimXferType type, const char *who); + +/** + * Increases the reference count on a GaimXfer. + * Please call gaim_xfer_unref later. + * + * @param xfer A file transfer handle. + */ +void gaim_xfer_ref(GaimXfer *xfer); + +/** + * Decreases the reference count on a GaimXfer. + * If the reference reaches 0, gaim_xfer_destroy (an internal function) + * will destroy the xfer. It calls the ui destroy cb first. + * Since the core keeps a ref on the xfer, only an erroneous call to + * this function will destroy the xfer while still in use. + * + * @param xfer A file transfer handle. + */ +void gaim_xfer_unref(GaimXfer *xfer); + +/** + * Requests confirmation for a file transfer from the user. If receiving + * a file which is known at this point, this requests user to accept and + * save the file. If the filename is unknown (not set) this only requests user + * to accept the file transfer. In this case protocol must call this function + * again once the filename is available. + * + * @param xfer The file transfer to request confirmation on. + */ +void gaim_xfer_request(GaimXfer *xfer); + +/** + * Called if the user accepts the file transfer request. + * + * @param xfer The file transfer. + * @param filename The filename. + */ +void gaim_xfer_request_accepted(GaimXfer *xfer, const char *filename); + +/** + * Called if the user rejects the file transfer request. + * + * @param xfer The file transfer. + */ +void gaim_xfer_request_denied(GaimXfer *xfer); + +/** + * Returns the type of file transfer. + * + * @param xfer The file transfer. + * + * @return The type of the file transfer. + */ +GaimXferType gaim_xfer_get_type(const GaimXfer *xfer); + +/** + * Returns the account the file transfer is using. + * + * @param xfer The file transfer. + * + * @return The account. + */ +GaimAccount *gaim_xfer_get_account(const GaimXfer *xfer); + +/** + * Returns the status of the xfer. + * + * @param xfer The file transfer. + * + * @return The status. + */ +GaimXferStatusType gaim_xfer_get_status(const GaimXfer *xfer); + +/** + * Returns true if the file transfer was canceled. + * + * @param xfer The file transfer. + * + * @return Whether or not the transfer was canceled. + */ +gboolean gaim_xfer_is_canceled(const GaimXfer *xfer); + +/** + * Returns the completed state for a file transfer. + * + * @param xfer The file transfer. + * + * @return The completed state. + */ +gboolean gaim_xfer_is_completed(const GaimXfer *xfer); + +/** + * Returns the name of the file being sent or received. + * + * @param xfer The file transfer. + * + * @return The filename. + */ +const char *gaim_xfer_get_filename(const GaimXfer *xfer); + +/** + * Returns the file's destination filename, + * + * @param xfer The file transfer. + * + * @return The destination filename. + */ +const char *gaim_xfer_get_local_filename(const GaimXfer *xfer); + +/** + * Returns the number of bytes sent (or received) so far. + * + * @param xfer The file transfer. + * + * @return The number of bytes sent. + */ +size_t gaim_xfer_get_bytes_sent(const GaimXfer *xfer); + +/** + * Returns the number of bytes remaining to send or receive. + * + * @param xfer The file transfer. + * + * @return The number of bytes remaining. + */ +size_t gaim_xfer_get_bytes_remaining(const GaimXfer *xfer); + +/** + * Returns the size of the file being sent or received. + * + * @param xfer The file transfer. + * + * @return The total size of the file. + */ +size_t gaim_xfer_get_size(const GaimXfer *xfer); + +/** + * Returns the current percentage of progress of the transfer. + * + * This is a number between 0 (0%) and 1 (100%). + * + * @param xfer The file transfer. + * + * @return The percentage complete. + */ +double gaim_xfer_get_progress(const GaimXfer *xfer); + +/** + * Returns the local port number in the file transfer. + * + * @param xfer The file transfer. + * + * @return The port number on this end. + */ +unsigned int gaim_xfer_get_local_port(const GaimXfer *xfer); + +/** + * Returns the remote IP address in the file transfer. + * + * @param xfer The file transfer. + * + * @return The IP address on the other end. + */ +const char *gaim_xfer_get_remote_ip(const GaimXfer *xfer); + +/** + * Returns the remote port number in the file transfer. + * + * @param xfer The file transfer. + * + * @return The port number on the other end. + */ +unsigned int gaim_xfer_get_remote_port(const GaimXfer *xfer); + +/** + * Sets the completed state for the file transfer. + * + * @param xfer The file transfer. + * @param completed The completed state. + */ +void gaim_xfer_set_completed(GaimXfer *xfer, gboolean completed); + +/** + * Sets the filename for the file transfer. + * + * @param xfer The file transfer. + * @param message The message. + */ +void gaim_xfer_set_message(GaimXfer *xfer, const char *message); + +/** + * Sets the filename for the file transfer. + * + * @param xfer The file transfer. + * @param filename The filename. + */ +void gaim_xfer_set_filename(GaimXfer *xfer, const char *filename); + +/** + * Sets the local filename for the file transfer. + * + * @param xfer The file transfer. + * @param filename The filename + */ +void gaim_xfer_set_local_filename(GaimXfer *xfer, const char *filename); + +/** + * Sets the size of the file in a file transfer. + * + * @param xfer The file transfer. + * @param size The size of the file. + */ +void gaim_xfer_set_size(GaimXfer *xfer, size_t size); + +/** + * Returns the UI operations structure for a file transfer. + * + * @param xfer The file transfer. + * + * @return The UI operations structure. + */ +GaimXferUiOps *gaim_xfer_get_ui_ops(const GaimXfer *xfer); + +/** + * Sets the read function for the file transfer. + * + * @param xfer The file transfer. + * @param fnc The read function. + */ +void gaim_xfer_set_read_fnc(GaimXfer *xfer, + gssize (*fnc)(guchar **, GaimXfer *)); + +/** + * Sets the write function for the file transfer. + * + * @param xfer The file transfer. + * @param fnc The write function. + */ +void gaim_xfer_set_write_fnc(GaimXfer *xfer, + gssize (*fnc)(const guchar *, size_t, GaimXfer *)); + +/** + * Sets the acknowledge function for the file transfer. + * + * @param xfer The file transfer. + * @param fnc The acknowledge function. + */ +void gaim_xfer_set_ack_fnc(GaimXfer *xfer, + void (*fnc)(GaimXfer *, const guchar *, size_t)); + +/** + * Sets the function to be called if the request is denied. + * + * @param xfer The file transfer. + * @param fnc The request denied prpl callback. + */ +void gaim_xfer_set_request_denied_fnc(GaimXfer *xfer, void (*fnc)(GaimXfer *)); + +/** + * Sets the transfer initialization function for the file transfer. + * + * This function is required, and must call gaim_xfer_start() with + * the necessary parameters. This will be called if the file transfer + * is accepted by the user. + * + * @param xfer The file transfer. + * @param fnc The transfer initialization function. + */ +void gaim_xfer_set_init_fnc(GaimXfer *xfer, void (*fnc)(GaimXfer *)); + +/** + * Sets the start transfer function for the file transfer. + * + * @param xfer The file transfer. + * @param fnc The start transfer function. + */ +void gaim_xfer_set_start_fnc(GaimXfer *xfer, void (*fnc)(GaimXfer *)); + +/** + * Sets the end transfer function for the file transfer. + * + * @param xfer The file transfer. + * @param fnc The end transfer function. + */ +void gaim_xfer_set_end_fnc(GaimXfer *xfer, void (*fnc)(GaimXfer *)); + +/** + * Sets the cancel send function for the file transfer. + * + * @param xfer The file transfer. + * @param fnc The cancel send function. + */ +void gaim_xfer_set_cancel_send_fnc(GaimXfer *xfer, void (*fnc)(GaimXfer *)); + +/** + * Sets the cancel receive function for the file transfer. + * + * @param xfer The file transfer. + * @param fnc The cancel receive function. + */ +void gaim_xfer_set_cancel_recv_fnc(GaimXfer *xfer, void (*fnc)(GaimXfer *)); + +/** + * Reads in data from a file transfer stream. + * + * @param xfer The file transfer. + * @param buffer The buffer that will be created to contain the data. + * + * @return The number of bytes read, or -1. + */ +gssize gaim_xfer_read(GaimXfer *xfer, guchar **buffer); + +/** + * Writes data to a file transfer stream. + * + * @param xfer The file transfer. + * @param buffer The buffer to read the data from. + * @param size The number of bytes to write. + * + * @return The number of bytes written, or -1. + */ +gssize gaim_xfer_write(GaimXfer *xfer, const guchar *buffer, gsize size); + +/** + * Starts a file transfer. + * + * Either @a fd must be specified or @a ip and @a port on a + * file receive transfer. On send, @a fd must be specified, and + * @a ip and @a port are ignored. + * + * @param xfer The file transfer. + * @param fd The file descriptor for the socket. + * @param ip The IP address to connect to. + * @param port The port to connect to. + */ +void gaim_xfer_start(GaimXfer *xfer, int fd, const char *ip, + unsigned int port); + +/** + * Ends a file transfer. + * + * @param xfer The file transfer. + */ +void gaim_xfer_end(GaimXfer *xfer); + +/** + * Adds a new file transfer to the list of file transfers. Call this only + * if you are not using gaim_xfer_start. + * + * @param xfer The file transfer. + */ +void gaim_xfer_add(GaimXfer *xfer); + +/** + * Cancels a file transfer on the local end. + * + * @param xfer The file transfer. + */ +void gaim_xfer_cancel_local(GaimXfer *xfer); + +/** + * Cancels a file transfer from the remote end. + * + * @param xfer The file transfer. + */ +void gaim_xfer_cancel_remote(GaimXfer *xfer); + +/** + * Displays a file transfer-related error message. + * + * This is a wrapper around gaim_notify_error(), which automatically + * specifies a title ("File transfer to user failed" or + * "File Transfer from user failed"). + * + * @param type The type of file transfer. + * @param account The account sending or receiving the file. + * @param who The user on the other end of the transfer. + * @param msg The message to display. + */ +void gaim_xfer_error(GaimXferType type, GaimAccount *account, const char *who, const char *msg); + +/** + * Updates file transfer progress. + * + * @param xfer The file transfer. + */ +void gaim_xfer_update_progress(GaimXfer *xfer); + +/** + * Displays a file transfer-related message in the conversation window + * + * This is a wrapper around gaim_conversation_write + * + * @param xfer The file transfer to which this message relates. + * @param message The message to display. + * @param is_error Is this an error message?. + */ +void gaim_xfer_conversation_write(GaimXfer *xfer, char *message, gboolean is_error); + +/*@}*/ + +/**************************************************************************/ +/** @name UI Registration Functions */ +/**************************************************************************/ +/*@{*/ + +/** + * Returns the handle to the file transfer subsystem + * + * @return The handle + */ +void *gaim_xfers_get_handle(void); + +/** + * Initializes the file transfer subsystem + */ +void gaim_xfers_init(void); + +/** + * Uninitializes the file transfer subsystem + */ +void gaim_xfers_uninit(void); + +/** + * Sets the UI operations structure to be used in all gaim file transfers. + * + * @param ops The UI operations structure. + */ +void gaim_xfers_set_ui_ops(GaimXferUiOps *ops); + +/** + * Returns the UI operations structure to be used in all gaim file transfers. + * + * @return The UI operations structure. + */ +GaimXferUiOps *gaim_xfers_get_ui_ops(void); + +/*@}*/ + +#ifdef __cplusplus +} +#endif + +#endif /* _GAIM_FT_H_ */ diff -r d10dda2777a9 -r b63ebf84c42b core/gaim-client-example.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/gaim-client-example.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,39 @@ +#define DBUS_API_SUBJECT_TO_CHANGE + +#include +#include + +#include "gaim-client.h" + +/* + This example demonstrates how to use libgaim-client to communicate + with gaim. The names and signatures of functions provided by + libgaim-client are the same as those in gaim. However, all + structures (such as GaimAccount) are opaque, that is, you can only + use pointer to them. In fact, these pointers DO NOT actually point + to anything, they are just integer identifiers of assigned to these + structures by gaim. So NEVER try to dereference these pointers. + Integer ids as disguised as pointers to provide type checking and + prevent mistakes such as passing an id of GaimAccount when an id of + GaimBuddy is expected. According to glib manual, this technique is + portable. +*/ + +int main (int argc, char **argv) +{ + GList *alist, *node; + + gaim_init(); + + alist = gaim_accounts_get_all(); + for (node = alist; node != NULL; node = node->next) + { + GaimAccount *account = (GaimAccount*) node->data; + char *name = gaim_account_get_username(account); + g_print("Name: %s\n", name); + g_free(name); + } + g_list_free(alist); + + return 0; +} diff -r d10dda2777a9 -r b63ebf84c42b core/gaim-client.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/gaim-client.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,81 @@ +#define DBUS_API_SUBJECT_TO_CHANGE + +#include +#include +#include + +#include "dbus-gaim.h" +#include "gaim-client-bindings.h" + +static DBusGConnection *bus; +static DBusGProxy *gaim_proxy; + +static GList *garray_int_to_glist(GArray *array) +{ + GList *list = NULL; + int i; + + for (i = 0; i < array->len; i++) + list = g_list_append(list, GINT_TO_POINTER(g_array_index(array,gint,i))); + + g_array_free(array, TRUE); + return list; +} + +static GSList *garray_int_to_gslist(GArray *array) +{ + GSList *list = NULL; + int i; + + for (i = 0; i < array->len; i++) + list = g_slist_append(list, GINT_TO_POINTER(g_array_index(array,gint,i))); + + g_array_free(array, TRUE); + return list; +} + +#include "gaim-client-bindings.c" + +static void lose(const char *fmt, ...) G_GNUC_NORETURN G_GNUC_PRINTF (1, 2); +static void lose_gerror(const char *prefix, GError *error) G_GNUC_NORETURN; + +static void +lose(const char *str, ...) +{ + va_list args; + + va_start(args, str); + + vfprintf(stderr, str, args); + fputc('\n', stderr); + + va_end(args); + + exit(1); +} + +static void +lose_gerror(const char *prefix, GError *error) +{ + lose("%s: %s", prefix, error->message); +} + +void gaim_init(void) +{ + GError *error = NULL; + + g_type_init (); + + bus = dbus_g_bus_get (DBUS_BUS_SESSION, &error); + if (!bus) + lose_gerror ("Couldn't connect to session bus", error); + + gaim_proxy = dbus_g_proxy_new_for_name (bus, + DBUS_SERVICE_GAIM, + DBUS_PATH_GAIM, + DBUS_INTERFACE_GAIM); + + if (!gaim_proxy) + lose_gerror ("Couldn't connect to the Gaim Service", error); +} + diff -r d10dda2777a9 -r b63ebf84c42b core/gaim-client.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/gaim-client.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,13 @@ +#ifndef _GAIM_CLIENT_H_INCLUDED_ +#define _GAIM_CLIENT_H_INCLUDED_ + +#include +#include "gaim-client-bindings.h" + +G_BEGIN_DECLS + +void gaim_init(void); + +G_END_DECLS + +#endif diff -r d10dda2777a9 -r b63ebf84c42b core/gaim-notifications-example --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/gaim-notifications-example Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,86 @@ +#!/usr/bin/env python + +# This is a simple gaim notification server. +# It shows notifications when your buddy signs on or you get an IM message. +# +# This script requires Python 2.4 and PyGTK bindings +# +# Note that all function names are resolved dynamically, no +# gaim-specific library is needed. + +import dbus +import dbus.glib +import dbus.decorators +import gobject +import os + +def ensureimconversation(conversation, account, name): + if conversation != 0: + return conversation + else: + # 1 = GAIM_CONV_IM + return gaim.GaimConversationNew(1, account, name) + +def receivedimmsg(account, name, message, conversation, flags): + buddy = gaim.GaimFindBuddy(account, name) + if buddy != 0: + alias = gaim.GaimBuddyGetAlias(buddy) + else: + alias = name + + text = "%s says %s" % (alias, message) + code = os.spawnlp(os.P_WAIT, "xmessage", "xmessage", "-buttons", + "'So what?','Show me',Close,Abuse", text) + + if code == 101: # so what? + pass + else: + conversation = ensureimconversation(conversation, account, name) + + if code == 102: # show me + window = gaim.GaimConversationGetWindow(conversation) + gaim.GaimConvWindowRaise(window) + + if code == 103: # close + gaim.GaimConversationDestroy(conversation) + + if code == 104: # abuse + im = gaim.GaimConversationGetImData(conversation) + gaim.GaimConvImSend(im, "Go away you f...") + + +def buddysignedon(buddyid): + alias = gaim.GaimBuddyGetAlias(buddyid) + text = "%s is online" % alias + + code = os.spawnlp(os.P_WAIT, "xmessage", "xmessage", "-buttons", + "'So what?','Let's talk'", text) + + if code == 101: # so what? + pass + + if code == 102: # talk + name = gaim.GaimBuddyGetName(buddyid) + account = gaim.GaimBuddyGetAccount(buddyid) + gaim.GaimConversationNew(1, account, name) + + +bus = dbus.SessionBus() +obj = bus.get_object("net.sf.gaim.GaimService", "/net/sf/gaim/GaimObject") +gaim = dbus.Interface(obj, "net.sf.gaim.GaimInterface") + +bus.add_signal_receiver(receivedimmsg, + dbus_interface = "net.sf.gaim.GaimInterface", + signal_name = "ReceivedImMsg") + +bus.add_signal_receiver(buddysignedon, + dbus_interface = "net.sf.gaim.GaimInterface", + signal_name = "BuddySignedOn") + +print "This is a simple gaim notification server." +print "It shows notifications when your buddy signs on or you get an IM message." + +loop = gobject.MainLoop() +loop.run() + + diff -r d10dda2777a9 -r b63ebf84c42b core/gaim-remote --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/gaim-remote Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,212 @@ +#!/usr/bin/python + +import dbus +import re +import urllib +import sys + +import xml.dom.minidom + +xml.dom.minidom.Element.all = xml.dom.minidom.Element.getElementsByTagName + +obj = dbus.SessionBus().get_object("net.sf.gaim.GaimService", "/net/sf/gaim/GaimObject") +gaim = dbus.Interface(obj, "net.sf.gaim.GaimInterface") + +class CheckedObject: + def __init__(self, obj): + self.obj = obj + + def __getattr__(self, attr): + return CheckedAttribute(self, attr) + +class CheckedAttribute: + def __init__(self, cobj, attr): + self.cobj = cobj + self.attr = attr + + def __call__(self, *args): + result = self.cobj.obj.__getattr__(self.attr)(*args) + if result == 0: + raise "Error: " + self.attr + " " + str(args) + " returned " + str(result) + return result + +cgaim = CheckedObject(gaim) + +urlregexp = r"^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?" + +def extendlist(list, length, fill): + if len(list) < length: + return list + [fill] * (length - len(list)) + else: + return list + +def convert(value): + try: + return int(value) + except: + return value + +def findaccount(accountname, protocolname): + try: + # prefer connected accounts + account = cgaim.GaimAccountsFindConnected(accountname, protocolname) + return account + except: + # try to get any account and connect it + account = cgaim.GaimAccountsFindAny(accountname, protocolname) + gaim.GaimAccountSetStatusVargs(account, "online", 1) + gaim.GaimAccountConnect(account) + return account + + +def execute(uri): + match = re.match(urlregexp, uri) + protocol = match.group(2) + if protocol == "aim" or protocol == "icq": + protocol = "oscar" + if protocol is not None: + protocol = "prpl-" + protocol + command = match.group(5) + paramstring = match.group(7) + params = {} + if paramstring is not None: + for param in paramstring.split("&"): + key, value = extendlist(param.split("=",1), 2, "") + params[key] = urllib.unquote(value) + + accountname = params.get("account", "") + + if command == "goim": + account = findaccount(accountname, protocol) + conversation = cgaim.GaimConversationNew(1, account, params["screenname"]) + if "message" in params: + im = cgaim.GaimConversationGetImData(conversation) + gaim.GaimConvImSend(im, params["message"]) + return None + + elif command == "gochat": + account = findaccount(accountname, protocol) + connection = cgaim.GaimAccountGetConnection(account) + return gaim.ServJoinChat(connection, params) + + elif command == "addbuddy": + account = findaccount(accountname, protocol) + return cgaim.GaimBlistRequestAddBuddy(account, params["screenname"], + params.get("group", ""), "") + + elif command == "setstatus": + current = gaim.GaimSavedstatusGetCurrent() + + if "status" in params: + status_id = params["status"] + status_type = gaim.GaimPrimitiveGetTypeFromId(status_id) + else: + status_type = gaim.GaimSavedStatusGetType(current) + status_id = gaim.GaimPrimitiveGetIdFromType(status_type) + + if "message" in params: + message = params["message"]; + else: + message = gaim.GaimSavedstatusGetMessage(current) + + if "account" in params: + accounts = [cgaim.GaimAccountsFindAny(accountname, protocol)] + + for account in accounts: + status = gaim.GaimAccountGetStatus(account, status_id) + type = gaim.GaimStatusGetType(status) + gaim.GaimSavedstatusSetSubstatus(current, account, type, message) + gaim.GaimSavedstatusActivateForAccount(current, account) + else: + accounts = gaim.GaimAccountsGetAllActive() + saved = gaim.GaimSavedstatusNew("", status_type) + gaim.GaimSavedstatusSetMessage(saved, message) + gaim.GaimSavedstatusActivate(saved) + + return None + + elif command == "getinfo": + account = findaccount(accountname, protocol) + connection = cgaim.GaimAccountGetConnection(account) + return gaim.ServGetInfo(connection, params["screenname"]) + + elif command == "quit": + return gaim.GaimCoreQuit() + + elif command == "uri": + return None + + else: + match = re.match(r"(\w+)\s*\(([^)]*)\)", command) + if match is not None: + name = match.group(1) + argstr = match.group(2) + if argstr == "": + args = [] + else: + args = argstr.split(",") + fargs = [] + for arg in args: + fargs.append(convert(arg.strip())) + return gaim.__getattr__(name)(*fargs) + else: + # introspect the object to get parameter names and types + # this is slow because the entire introspection info must be downloaded + data = dbus.Interface(obj, "org.freedesktop.DBus.Introspectable").\ + Introspect() + introspect = xml.dom.minidom.parseString(data).documentElement + for method in introspect.all("method"): + if command == method.getAttribute("name"): + methodparams = [] + for arg in method.all("arg"): + if arg.getAttribute("direction") == "in": + value = params[arg.getAttribute("name")] + type = arg.getAttribute("type") + if type == "s": + methodparams.append(value) + elif type == "i": + methodparams.append(int(value)) + else: + raise "Don't know how to handle type \"%s\"" % type + return gaim.__getattr__(command)(*methodparams) + raise "Unknown command: %s" % command + + +if len(sys.argv) == 1: + print """This program uses DBus to communicate with gaim. + +Usage: + + %s "command1" "command2" ... + +Each command is of one of the three types: + + [protocol:]commandname?param1=value1¶m2=value2&... + FunctionName?param1=value1¶m2=value2&... + FunctionName(value1,value2,...) + +The second and third form are provided for completeness but their use +is not recommended; use gaim-send or gaim-send-async instead. The +second form uses introspection to find out the parameter names and +their types, therefore it is rather slow. + +Examples of commands: + + jabber:goim?screenname=testone@localhost&message=hi + jabber:gochat?room=TestRoom&server=conference.localhost + jabber:getinfo?screenname=testone@localhost + jabber:addbuddy?screenname=my friend + + setstatus?status=away&message=don't disturb + quit + + GaimAccountsFindConnected?name=&protocol=prpl-jabber + GaimAccountFindConnected(,prpl-jabber) +""" % sys.argv[0] + +for arg in sys.argv[1:]: + output = execute(arg) + + if (output != None): + print output + diff -r d10dda2777a9 -r b63ebf84c42b core/gaim-send --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/gaim-send Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,32 @@ +#!/bin/bash + +METHOD_NAME=$1 + +if test -z "$METHOD_NAME" +then + cat < +#else +#ifndef _AIX +char *alloca (); +#endif +#endif /* alloca.h */ +#endif /* not __GNUC__ */ + +#if !__STDC__ && !defined(const) && IN_GCC +#define const +#endif + +/* This tells Alpha OSF/1 not to define a getopt prototype in . */ +#ifndef _NO_PROTO +#define _NO_PROTO +#endif + +#include + +/* Comment out all this code if we are using the GNU C Library, and are not + actually compiling the library itself. This code is part of the GNU C + Library, but also included in many other GNU distributions. Compiling + and linking in this code is a waste when using the GNU C library + (especially if it is a shared library). Rather than having every GNU + program understand `configure --with-gnu-libc' and omit the object files, + it is simpler to just do this in the source for each such file. */ + +#if defined (_LIBC) || !defined (__GNU_LIBRARY__) + + +/* This needs to come after some library #include + to get __GNU_LIBRARY__ defined. */ +#ifdef __GNU_LIBRARY__ +#undef alloca +/* Don't include stdlib.h for non-GNU C libraries because some of them + contain conflicting prototypes for getopt. */ +#include +#else /* Not GNU C library. */ +#define __alloca alloca +#endif /* GNU C library. */ + +/* If GETOPT_COMPAT is defined, `+' as well as `--' can introduce a + long-named option. Because this is not POSIX.2 compliant, it is + being phased out. */ +/* #define GETOPT_COMPAT */ + +/* This version of `getopt' appears to the caller like standard Unix `getopt' + but it behaves differently for the user, since it allows the user + to intersperse the options with the other arguments. + + As `getopt' works, it permutes the elements of ARGV so that, + when it is done, all the options precede everything else. Thus + all application programs are extended to handle flexible argument order. + + Setting the environment variable POSIXLY_CORRECT disables permutation. + Then the behavior is completely standard. + + GNU application programs can use a third alternative mode in which + they can distinguish the relative order of options and other arguments. */ + +#include "getopt.h" + +/* For communication from `getopt' to the caller. + When `getopt' finds an option that takes an argument, + the argument value is returned here. + Also, when `ordering' is RETURN_IN_ORDER, + each non-option ARGV-element is returned here. */ + +char *optarg = 0; + +/* Index in ARGV of the next element to be scanned. + This is used for communication to and from the caller + and for communication between successive calls to `getopt'. + + On entry to `getopt', zero means this is the first call; initialize. + + When `getopt' returns EOF, this is the index of the first of the + non-option elements that the caller should itself scan. + + Otherwise, `optind' communicates from one call to the next + how much of ARGV has been scanned so far. */ + +/* XXX 1003.2 says this must be 1 before any call. */ +int optind = 0; + +/* The next char to be scanned in the option-element + in which the last option character we returned was found. + This allows us to pick up the scan where we left off. + + If this is zero, or a null string, it means resume the scan + by advancing to the next ARGV-element. */ + +static char *nextchar; + +/* Callers store zero here to inhibit the error message + for unrecognized options. */ + +int opterr = 1; + +/* Set to an option character which was unrecognized. + This must be initialized on some systems to avoid linking in the + system's own getopt implementation. */ + +int optopt = '?'; + +/* Describe how to deal with options that follow non-option ARGV-elements. + + If the caller did not specify anything, + the default is REQUIRE_ORDER if the environment variable + POSIXLY_CORRECT is defined, PERMUTE otherwise. + + REQUIRE_ORDER means don't recognize them as options; + stop option processing when the first non-option is seen. + This is what Unix does. + This mode of operation is selected by either setting the environment + variable POSIXLY_CORRECT, or using `+' as the first character + of the list of option characters. + + PERMUTE is the default. We permute the contents of ARGV as we scan, + so that eventually all the non-options are at the end. This allows options + to be given in any order, even with programs that were not written to + expect this. + + RETURN_IN_ORDER is an option available to programs that were written + to expect options and other ARGV-elements in any order and that care about + the ordering of the two. We describe each non-option ARGV-element + as if it were the argument of an option with character code 1. + Using `-' as the first character of the list of option characters + selects this mode of operation. + + The special argument `--' forces an end of option-scanning regardless + of the value of `ordering'. In the case of RETURN_IN_ORDER, only + `--' can cause `getopt' to return EOF with `optind' != ARGC. */ + +static enum +{ + REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER +} ordering; + +#ifdef __GNU_LIBRARY__ +/* We want to avoid inclusion of string.h with non-GNU libraries + because there are many ways it can cause trouble. + On some systems, it contains special magic macros that don't work + in GCC. */ +#include +#define my_index strchr +#define my_bcopy(src, dst, n) memcpy ((dst), (src), (n)) +#else + +/* Avoid depending on library functions or files + whose names are inconsistent. */ + +char *getenv (); + +static char * +my_index (str, chr) + const char *str; + int chr; +{ + while (*str) + { + if (*str == chr) + return (char *) str; + str++; + } + return 0; +} + +static void +my_bcopy (from, to, size) + const char *from; + char *to; + int size; +{ + int i; + for (i = 0; i < size; i++) + to[i] = from[i]; +} +#endif /* GNU C library. */ + +/* Handle permutation of arguments. */ + +/* Describe the part of ARGV that contains non-options that have + been skipped. `first_nonopt' is the index in ARGV of the first of them; + `last_nonopt' is the index after the last of them. */ + +static int first_nonopt; +static int last_nonopt; + +/* Exchange two adjacent subsequences of ARGV. + One subsequence is elements [first_nonopt,last_nonopt) + which contains all the non-options that have been skipped so far. + The other is elements [last_nonopt,optind), which contains all + the options processed since those non-options were skipped. + + `first_nonopt' and `last_nonopt' are relocated so that they describe + the new indices of the non-options in ARGV after they are moved. */ + +static void +exchange (argv) + char **argv; +{ + int nonopts_size = (last_nonopt - first_nonopt) * sizeof (char *); + char **temp = (char **) __alloca (nonopts_size); + + /* Interchange the two blocks of data in ARGV. */ + + my_bcopy ((char *) &argv[first_nonopt], (char *) temp, nonopts_size); + my_bcopy ((char *) &argv[last_nonopt], (char *) &argv[first_nonopt], + (optind - last_nonopt) * sizeof (char *)); + my_bcopy ((char *) temp, + (char *) &argv[first_nonopt + optind - last_nonopt], + nonopts_size); + + /* Update records for the slots the non-options now occupy. */ + + first_nonopt += (optind - last_nonopt); + last_nonopt = optind; +} + +/* Scan elements of ARGV (whose length is ARGC) for option characters + given in OPTSTRING. + + If an element of ARGV starts with '-', and is not exactly "-" or "--", + then it is an option element. The characters of this element + (aside from the initial '-') are option characters. If `getopt' + is called repeatedly, it returns successively each of the option characters + from each of the option elements. + + If `getopt' finds another option character, it returns that character, + updating `optind' and `nextchar' so that the next call to `getopt' can + resume the scan with the following option character or ARGV-element. + + If there are no more option characters, `getopt' returns `EOF'. + Then `optind' is the index in ARGV of the first ARGV-element + that is not an option. (The ARGV-elements have been permuted + so that those that are not options now come last.) + + OPTSTRING is a string containing the legitimate option characters. + If an option character is seen that is not listed in OPTSTRING, + return '?' after printing an error message. If you set `opterr' to + zero, the error message is suppressed but we still return '?'. + + If a char in OPTSTRING is followed by a colon, that means it wants an arg, + so the following text in the same ARGV-element, or the text of the following + ARGV-element, is returned in `optarg'. Two colons mean an option that + wants an optional arg; if there is text in the current ARGV-element, + it is returned in `optarg', otherwise `optarg' is set to zero. + + If OPTSTRING starts with `-' or `+', it requests different methods of + handling the non-option ARGV-elements. + See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above. + + Long-named options begin with `--' instead of `-'. + Their names may be abbreviated as long as the abbreviation is unique + or is an exact match for some defined option. If they have an + argument, it follows the option name in the same ARGV-element, separated + from the option name by a `=', or else the in next ARGV-element. + When `getopt' finds a long-named option, it returns 0 if that option's + `flag' field is nonzero, the value of the option's `val' field + if the `flag' field is zero. + + The elements of ARGV aren't really const, because we permute them. + But we pretend they're const in the prototype to be compatible + with other systems. + + LONGOPTS is a vector of `struct option' terminated by an + element containing a name which is zero. + + LONGIND returns the index in LONGOPT of the long-named option found. + It is only valid when a long-named option has been found by the most + recent call. + + If LONG_ONLY is nonzero, '-' as well as '--' can introduce + long-named options. */ + +int +_getopt_internal (argc, argv, optstring, longopts, longind, long_only) + int argc; + char *const *argv; + const char *optstring; + const struct option *longopts; + int *longind; + int long_only; +{ + int option_index; + + optarg = 0; + + /* Initialize the internal data when the first call is made. + Start processing options with ARGV-element 1 (since ARGV-element 0 + is the program name); the sequence of previously skipped + non-option ARGV-elements is empty. */ + + if (optind == 0) + { + first_nonopt = last_nonopt = optind = 1; + + nextchar = NULL; + + /* Determine how to handle the ordering of options and nonoptions. */ + + if (optstring[0] == '-') + { + ordering = RETURN_IN_ORDER; + ++optstring; + } + else if (optstring[0] == '+') + { + ordering = REQUIRE_ORDER; + ++optstring; + } + else if (getenv ("POSIXLY_CORRECT") != NULL) + ordering = REQUIRE_ORDER; + else + ordering = PERMUTE; + } + + if (nextchar == NULL || *nextchar == '\0') + { + if (ordering == PERMUTE) + { + /* If we have just processed some options following some non-options, + exchange them so that the options come first. */ + + if (first_nonopt != last_nonopt && last_nonopt != optind) + exchange ((char **) argv); + else if (last_nonopt != optind) + first_nonopt = optind; + + /* Now skip any additional non-options + and extend the range of non-options previously skipped. */ + + while (optind < argc + && (argv[optind][0] != '-' || argv[optind][1] == '\0') +#ifdef GETOPT_COMPAT + && (longopts == NULL + || argv[optind][0] != '+' || argv[optind][1] == '\0') +#endif /* GETOPT_COMPAT */ + ) + optind++; + last_nonopt = optind; + } + + /* Special ARGV-element `--' means premature end of options. + Skip it like a null option, + then exchange with previous non-options as if it were an option, + then skip everything else like a non-option. */ + + if (optind != argc && !strcmp (argv[optind], "--")) + { + optind++; + + if (first_nonopt != last_nonopt && last_nonopt != optind) + exchange ((char **) argv); + else if (first_nonopt == last_nonopt) + first_nonopt = optind; + last_nonopt = argc; + + optind = argc; + } + + /* If we have done all the ARGV-elements, stop the scan + and back over any non-options that we skipped and permuted. */ + + if (optind == argc) + { + /* Set the next-arg-index to point at the non-options + that we previously skipped, so the caller will digest them. */ + if (first_nonopt != last_nonopt) + optind = first_nonopt; + return EOF; + } + + /* If we have come to a non-option and did not permute it, + either stop the scan or describe it to the caller and pass it by. */ + + if ((argv[optind][0] != '-' || argv[optind][1] == '\0') +#ifdef GETOPT_COMPAT + && (longopts == NULL + || argv[optind][0] != '+' || argv[optind][1] == '\0') +#endif /* GETOPT_COMPAT */ + ) + { + if (ordering == REQUIRE_ORDER) + return EOF; + optarg = argv[optind++]; + return 1; + } + + /* We have found another option-ARGV-element. + Start decoding its characters. */ + + nextchar = (argv[optind] + 1 + + (longopts != NULL && argv[optind][1] == '-')); + } + + if (longopts != NULL + && ((argv[optind][0] == '-' + && (argv[optind][1] == '-' || long_only)) +#ifdef GETOPT_COMPAT + || argv[optind][0] == '+' +#endif /* GETOPT_COMPAT */ + )) + { + const struct option *p; + char *s = nextchar; + int exact = 0; + int ambig = 0; + const struct option *pfound = NULL; + int indfound; + + while (*s && *s != '=') + s++; + + /* Test all options for either exact match or abbreviated matches. */ + for (p = longopts, option_index = 0; p->name; + p++, option_index++) + if (!strncmp (p->name, nextchar, s - nextchar)) + { + if (s - nextchar == strlen (p->name)) + { + /* Exact match found. */ + pfound = p; + indfound = option_index; + exact = 1; + break; + } + else if (pfound == NULL) + { + /* First nonexact match found. */ + pfound = p; + indfound = option_index; + } + else + /* Second nonexact match found. */ + ambig = 1; + } + + if (ambig && !exact) + { + if (opterr) + fprintf (stderr, "%s: option `%s' is ambiguous\n", + argv[0], argv[optind]); + nextchar += strlen (nextchar); + optind++; + return '?'; + } + + if (pfound != NULL) + { + option_index = indfound; + optind++; + if (*s) + { + /* Don't test has_arg with >, because some C compilers don't + allow it to be used on enums. */ + if (pfound->has_arg) + optarg = s + 1; + else + { + if (opterr) + { + if (argv[optind - 1][1] == '-') + /* --option */ + fprintf (stderr, + "%s: option `--%s' doesn't allow an argument\n", + argv[0], pfound->name); + else + /* +option or -option */ + fprintf (stderr, + "%s: option `%c%s' doesn't allow an argument\n", + argv[0], argv[optind - 1][0], pfound->name); + } + nextchar += strlen (nextchar); + return '?'; + } + } + else if (pfound->has_arg == 1) + { + if (optind < argc) + optarg = argv[optind++]; + else + { + if (opterr) + fprintf (stderr, "%s: option `%s' requires an argument\n", + argv[0], argv[optind - 1]); + nextchar += strlen (nextchar); + return optstring[0] == ':' ? ':' : '?'; + } + } + nextchar += strlen (nextchar); + if (longind != NULL) + *longind = option_index; + if (pfound->flag) + { + *(pfound->flag) = pfound->val; + return 0; + } + return pfound->val; + } + /* Can't find it as a long option. If this is not getopt_long_only, + or the option starts with '--' or is not a valid short + option, then it's an error. + Otherwise interpret it as a short option. */ + if (!long_only || argv[optind][1] == '-' +#ifdef GETOPT_COMPAT + || argv[optind][0] == '+' +#endif /* GETOPT_COMPAT */ + || my_index (optstring, *nextchar) == NULL) + { + if (opterr) + { + if (argv[optind][1] == '-') + /* --option */ + fprintf (stderr, "%s: unrecognized option `--%s'\n", + argv[0], nextchar); + else + /* +option or -option */ + fprintf (stderr, "%s: unrecognized option `%c%s'\n", + argv[0], argv[optind][0], nextchar); + } + nextchar = (char *) ""; + optind++; + return '?'; + } + } + + /* Look at and handle the next option-character. */ + + { + char c = *nextchar++; + char *temp = my_index (optstring, c); + + /* Increment `optind' when we start to process its last character. */ + if (*nextchar == '\0') + ++optind; + + if (temp == NULL || c == ':') + { + if (opterr) + { +#if 0 + if (c < 040 || c >= 0177) + fprintf (stderr, "%s: unrecognized option, character code 0%o\n", + argv[0], c); + else + fprintf (stderr, "%s: unrecognized option `-%c'\n", argv[0], c); +#else + /* 1003.2 specifies the format of this message. */ + fprintf (stderr, "%s: illegal option -- %c\n", argv[0], c); +#endif + } + optopt = c; + return '?'; + } + if (temp[1] == ':') + { + if (temp[2] == ':') + { + /* This is an option that accepts an argument optionally. */ + if (*nextchar != '\0') + { + optarg = nextchar; + optind++; + } + else + optarg = 0; + nextchar = NULL; + } + else + { + /* This is an option that requires an argument. */ + if (*nextchar != '\0') + { + optarg = nextchar; + /* If we end this ARGV-element by taking the rest as an arg, + we must advance to the next element now. */ + optind++; + } + else if (optind == argc) + { + if (opterr) + { +#if 0 + fprintf (stderr, "%s: option `-%c' requires an argument\n", + argv[0], c); +#else + /* 1003.2 specifies the format of this message. */ + fprintf (stderr, "%s: option requires an argument -- %c\n", + argv[0], c); +#endif + } + optopt = c; + if (optstring[0] == ':') + c = ':'; + else + c = '?'; + } + else + /* We already incremented `optind' once; + increment it again when taking next ARGV-elt as argument. */ + optarg = argv[optind++]; + nextchar = NULL; + } + } + return c; + } +} + +int +getopt (argc, argv, optstring) + int argc; + char *const *argv; + const char *optstring; +{ + return _getopt_internal (argc, argv, optstring, + (const struct option *) 0, + (int *) 0, + 0); +} + +#endif /* _LIBC or not __GNU_LIBRARY__. */ + +#ifdef TEST + +/* Compile with -DTEST to make an executable for use in testing + the above definition of `getopt'. */ + +int +main (argc, argv) + int argc; + char **argv; +{ + int c; + int digit_optind = 0; + + while (1) + { + int this_option_optind = optind ? optind : 1; + + c = getopt (argc, argv, "abc:d:0123456789"); + if (c == EOF) + break; + + switch (c) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (digit_optind != 0 && digit_optind != this_option_optind) + printf ("digits occur in two different argv-elements.\n"); + digit_optind = this_option_optind; + printf ("option %c\n", c); + break; + + case 'a': + printf ("option a\n"); + break; + + case 'b': + printf ("option b\n"); + break; + + case 'c': + printf ("option c with value `%s'\n", optarg); + break; + + case '?': + break; + + default: + printf ("?? getopt returned character code 0%o ??\n", c); + } + } + + if (optind < argc) + { + printf ("non-option ARGV-elements: "); + while (optind < argc) + printf ("%s ", argv[optind++]); + printf ("\n"); + } + + exit (0); +} + +#endif /* TEST */ diff -r d10dda2777a9 -r b63ebf84c42b core/getopt.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/getopt.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,136 @@ +/* Declarations for getopt. + + NOTE: getopt is now part of the C library, so if you don't know what + "Keep this file name-space clean" means, talk to roland@gnu.ai.mit.edu + before changing it! + + 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, 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, 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#ifndef _GETOPT_H +#define _GETOPT_H 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/* For communication from `getopt' to the caller. + When `getopt' finds an option that takes an argument, + the argument value is returned here. + Also, when `ordering' is RETURN_IN_ORDER, + each non-option ARGV-element is returned here. */ + +extern char *optarg; + +/* Index in ARGV of the next element to be scanned. + This is used for communication to and from the caller + and for communication between successive calls to `getopt'. + + On entry to `getopt', zero means this is the first call; initialize. + + When `getopt' returns EOF, this is the index of the first of the + non-option elements that the caller should itself scan. + + Otherwise, `optind' communicates from one call to the next + how much of ARGV has been scanned so far. */ + +extern int optind; + +/* Callers store zero here to inhibit the error message `getopt' prints + for unrecognized options. */ + +extern int opterr; + +/* Set to an option character which was unrecognized. */ + +extern int optopt; + +/* Describe the long-named options requested by the application. + The LONG_OPTIONS argument to getopt_long or getopt_long_only is a vector + of `struct option' terminated by an element containing a name which is + zero. + + The field `has_arg' is: + no_argument (or 0) if the option does not take an argument, + required_argument (or 1) if the option requires an argument, + optional_argument (or 2) if the option takes an optional argument. + + If the field `flag' is not NULL, it points to a variable that is set + to the value given in the field `val' when the option is found, but + left unchanged if the option is not found. + + To have a long-named option do something other than set an `int' to + a compiled-in constant, such as set a value from `optarg', set the + option's `flag' field to zero and its `val' field to a nonzero + value (the equivalent single-letter option character, if there is + one). For long options that have a zero `flag' field, `getopt' + returns the contents of the `val' field. */ + +struct option +{ +#if __STDC__ + const char *name; +#else + char *name; +#endif + /* has_arg can't be an enum because some compilers complain about + type mismatches in all the code that assumes it is an int. */ + int has_arg; + int *flag; + int val; +}; + +/* Names for the values of the `has_arg' field of `struct option'. */ + +#define no_argument 0 +#define required_argument 1 +#define optional_argument 2 + +#if __STDC__ +#if defined(__GNU_LIBRARY__) +/* Many other libraries have conflicting prototypes for getopt, with + differences in the consts, in stdlib.h. To avoid compilation + errors, only prototype getopt for the GNU C library. */ +extern int getopt (int argc, char *const *argv, const char *shortopts); +#else /* not __GNU_LIBRARY__ */ +extern int getopt (); +#endif /* not __GNU_LIBRARY__ */ +extern int getopt_long (int argc, char *const *argv, const char *shortopts, + const struct option *longopts, int *longind); +extern int getopt_long_only (int argc, char *const *argv, + const char *shortopts, + const struct option *longopts, int *longind); + +/* Internal only. Users should not call this directly. */ +extern int _getopt_internal (int argc, char *const *argv, + const char *shortopts, + const struct option *longopts, int *longind, + int long_only); +#else /* not __STDC__ */ +extern int getopt (); +extern int getopt_long (); +extern int getopt_long_only (); + +extern int _getopt_internal (); +#endif /* not __STDC__ */ + +#ifdef __cplusplus +} +#endif + +#endif /* _GETOPT_H */ diff -r d10dda2777a9 -r b63ebf84c42b core/getopt1.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/getopt1.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,177 @@ +/* getopt_long and getopt_long_only entry points for GNU getopt. + 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, 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, 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "getopt.h" + +#if !__STDC__ && !defined(const) && IN_GCC +#define const +#endif + +#include + +/* Comment out all this code if we are using the GNU C Library, and are not + actually compiling the library itself. This code is part of the GNU C + Library, but also included in many other GNU distributions. Compiling + and linking in this code is a waste when using the GNU C library + (especially if it is a shared library). Rather than having every GNU + program understand `configure --with-gnu-libc' and omit the object files, + it is simpler to just do this in the source for each such file. */ + +#if defined (_LIBC) || !defined (__GNU_LIBRARY__) + + +/* This needs to come after some library #include + to get __GNU_LIBRARY__ defined. */ +#ifdef __GNU_LIBRARY__ +#include +#else +char *getenv (); +#endif + +#ifndef NULL +#define NULL 0 +#endif + +int +getopt_long (argc, argv, options, long_options, opt_index) + int argc; + char *const *argv; + const char *options; + const struct option *long_options; + int *opt_index; +{ + return _getopt_internal (argc, argv, options, long_options, opt_index, 0); +} + +/* Like getopt_long, but '-' as well as '--' can indicate a long option. + If an option that starts with '-' (not '--') doesn't match a long option, + but does match a short option, it is parsed as a short option + instead. */ + +int +getopt_long_only (argc, argv, options, long_options, opt_index) + int argc; + char *const *argv; + const char *options; + const struct option *long_options; + int *opt_index; +{ + return _getopt_internal (argc, argv, options, long_options, opt_index, 1); +} + + +#endif /* _LIBC or not __GNU_LIBRARY__. */ + +#ifdef TEST + +#include + +int +main (argc, argv) + int argc; + char **argv; +{ + int c; + int digit_optind = 0; + + while (1) + { + int this_option_optind = optind ? optind : 1; + int option_index = 0; + static struct option long_options[] = + { + {"add", 1, 0, 0}, + {"append", 0, 0, 0}, + {"delete", 1, 0, 0}, + {"verbose", 0, 0, 0}, + {"create", 0, 0, 0}, + {"file", 1, 0, 0}, + {0, 0, 0, 0} + }; + + c = getopt_long (argc, argv, "abc:d:0123456789", + long_options, &option_index); + if (c == EOF) + break; + + switch (c) + { + case 0: + printf ("option %s", long_options[option_index].name); + if (optarg) + printf (" with arg %s", optarg); + printf ("\n"); + break; + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (digit_optind != 0 && digit_optind != this_option_optind) + printf ("digits occur in two different argv-elements.\n"); + digit_optind = this_option_optind; + printf ("option %c\n", c); + break; + + case 'a': + printf ("option a\n"); + break; + + case 'b': + printf ("option b\n"); + break; + + case 'c': + printf ("option c with value `%s'\n", optarg); + break; + + case 'd': + printf ("option d with value `%s'\n", optarg); + break; + + case '?': + break; + + default: + printf ("?? getopt returned character code 0%o ??\n", c); + } + } + + if (optind < argc) + { + printf ("non-option ARGV-elements: "); + while (optind < argc) + printf ("%s ", argv[optind++]); + printf ("\n"); + } + + exit (0); +} + +#endif /* TEST */ diff -r d10dda2777a9 -r b63ebf84c42b core/idle.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/idle.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,258 @@ +/* + * 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 "idle.h" +#include "log.h" +#include "prefs.h" +#include "savedstatuses.h" +#include "signals.h" + +#define IDLEMARK 600 /* 10 minutes! */ +#define IDLE_CHECK_INTERVAL 5 /* 5 seconds */ + +typedef enum +{ + GAIM_IDLE_NOT_AWAY = 0, + GAIM_IDLE_AUTO_AWAY, + GAIM_IDLE_AWAY_BUT_NOT_AUTO_AWAY + +} GaimAutoAwayState; + +static GaimIdleUiOps *idle_ui_ops = NULL; + +/** + * This is needed for the I'dle Mak'er plugin to work correctly. We + * use it to determine if we're the ones who set our accounts idle + * or if someone else did it (the I'dle Mak'er plugin, for example). + * Basically we just keep track of which accounts were set idle by us, + * and then we'll only set these specific accounts unidle when the + * user returns. + */ +static GList *idled_accts = NULL; + +static guint idle_timer = 0; + +static time_t last_active_time = 0; + +static void +set_account_idle(GaimAccount *account, int time_idle) +{ + GaimPresence *presence; + + presence = gaim_account_get_presence(account); + + if (gaim_presence_is_idle(presence)) + /* This account is already idle! */ + return; + + gaim_debug_info("idle", "Setting %s idle %d seconds\n", + gaim_account_get_username(account), time_idle); + gaim_presence_set_idle(presence, TRUE, time(NULL) - time_idle); + idled_accts = g_list_prepend(idled_accts, account); +} + +static void +set_account_unidle(GaimAccount *account) +{ + GaimPresence *presence; + + presence = gaim_account_get_presence(account); + + idled_accts = g_list_remove(idled_accts, account); + + if (!gaim_presence_is_idle(presence)) + /* This account is already unidle! */ + return; + + gaim_debug_info("idle", "Setting %s unidle\n", + gaim_account_get_username(account)); + gaim_presence_set_idle(presence, FALSE, 0); +} + +/* + * This function should be called when you think your idle state + * may have changed. Maybe you're over the 10-minute mark and + * Gaim should start reporting idle time to the server. Maybe + * you've returned from being idle. Maybe your auto-away message + * should be set. + * + * There is no harm to calling this many many times, other than + * it will be kinda slow. This is called every 5 seconds by a + * timer set when Gaim starts. It is also called when + * you send an IM, a chat, etc. + * + * This function has 3 sections. + * 1. Get your idle time. It will query XScreenSaver or Windows + * or use the Gaim idle time. Whatever. + * 2. Set or unset your auto-away message. + * 3. Report your current idle time to the IM server. + */ +static gint +check_idleness() +{ + time_t time_idle; + gboolean auto_away; + const gchar *idle_reporting; + gboolean report_idle; + GList *l; + + gaim_signal_emit(gaim_blist_get_handle(), "update-idle"); + + idle_reporting = gaim_prefs_get_string("/core/away/idle_reporting"); + report_idle = TRUE; + if (!strcmp(idle_reporting, "system") && + (idle_ui_ops != NULL) && (idle_ui_ops->get_time_idle != NULL)) + { + /* Use system idle time (mouse or keyboard movement, etc.) */ + time_idle = idle_ui_ops->get_time_idle(); + } + else if (!strcmp(idle_reporting, "gaim")) + { + /* Use 'Gaim idle' */ + time_idle = time(NULL) - last_active_time; + } + else + { + /* Don't report idle time */ + time_idle = 0; + report_idle = FALSE; + } + + /* Auto-away stuff */ + auto_away = gaim_prefs_get_bool("/core/away/away_when_idle"); + if (auto_away && + (time_idle > (60 * gaim_prefs_get_int("/core/away/mins_before_away")))) + { + gaim_savedstatus_set_idleaway(TRUE); + } + else if (time_idle < 60 * gaim_prefs_get_int("/core/away/mins_before_away")) + { + gaim_savedstatus_set_idleaway(FALSE); + } + + /* Idle reporting stuff */ + if (report_idle && (time_idle >= IDLEMARK)) + { + for (l = gaim_connections_get_all(); l != NULL; l = l->next) + { + GaimConnection *gc = l->data; + set_account_idle(gaim_connection_get_account(gc), time_idle); + } + } + else if (!report_idle || (time_idle < IDLEMARK)) + { + while (idled_accts != NULL) + set_account_unidle(idled_accts->data); + } + + return TRUE; +} + +static void +im_msg_sent_cb(GaimAccount *account, const char *receiver, + const char *message, void *data) +{ + /* Check our idle time after an IM is sent */ + check_idleness(); +} + +static void +signing_on_cb(GaimConnection *gc, void *data) +{ + /* When signing on a new account, check if the account should be idle */ + check_idleness(); +} + +static void +signing_off_cb(GaimConnection *gc, void *data) +{ + GaimAccount *account; + + account = gaim_connection_get_account(gc); + set_account_unidle(account); +} + +void +gaim_idle_touch() +{ + time(&last_active_time); +} + +void +gaim_idle_set(time_t time) +{ + last_active_time = time; +} + +void +gaim_idle_set_ui_ops(GaimIdleUiOps *ops) +{ + idle_ui_ops = ops; +} + +GaimIdleUiOps * +gaim_idle_get_ui_ops(void) +{ + return idle_ui_ops; +} + +static void * +gaim_idle_get_handle() +{ + static int handle; + + return &handle; +} + +void +gaim_idle_init() +{ + /* Add the timer to check if we're idle */ + idle_timer = gaim_timeout_add(IDLE_CHECK_INTERVAL * 1000, check_idleness, NULL); + + gaim_signal_connect(gaim_conversations_get_handle(), "sent-im-msg", + gaim_idle_get_handle(), + GAIM_CALLBACK(im_msg_sent_cb), NULL); + gaim_signal_connect(gaim_connections_get_handle(), "signing-on", + gaim_idle_get_handle(), + GAIM_CALLBACK(signing_on_cb), NULL); + gaim_signal_connect(gaim_connections_get_handle(), "signing-off", + gaim_idle_get_handle(), + GAIM_CALLBACK(signing_off_cb), NULL); + + gaim_idle_touch(); +} + +void +gaim_idle_uninit() +{ + gaim_signals_disconnect_by_handle(gaim_idle_get_handle()); + + /* Remove the idle timer */ + if (idle_timer > 0) + gaim_timeout_remove(idle_timer); + idle_timer = 0; +} diff -r d10dda2777a9 -r b63ebf84c42b core/idle.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/idle.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,96 @@ +/** + * @file idle.h Idle API + * @ingroup core + * + * 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 + */ +#ifndef _GAIM_IDLE_H_ +#define _GAIM_IDLE_H_ + +/** + * Idle UI operations. + */ +typedef struct +{ + time_t (*get_time_idle)(void); +} GaimIdleUiOps; + +#ifdef __cplusplus +extern "C" { +#endif + +/**************************************************************************/ +/** @name Idle API */ +/**************************************************************************/ +/*@{*/ + +/** + * Touch our idle tracker. This signifies that the user is + * 'active'. The conversation code calls this when the + * user sends an IM, for example. + */ +void gaim_idle_touch(void); + +/** + * Fake our idle time by setting the time at which our + * accounts purportedly became idle. This is used by + * the I'dle Mak'er plugin. + */ +void gaim_idle_set(time_t time); + +/*@}*/ + +/**************************************************************************/ +/** @name Idle Subsystem */ +/**************************************************************************/ +/*@{*/ + +/** + * Sets the UI operations structure to be used for idle reporting. + * + * @param ops The UI operations structure. + */ +void gaim_idle_set_ui_ops(GaimIdleUiOps *ops); + +/** + * Returns the UI operations structure used for idle reporting. + * + * @return The UI operations structure in use. + */ +GaimIdleUiOps *gaim_idle_get_ui_ops(void); + +/** + * Initializes the idle system. + */ +void gaim_idle_init(void); + +/** + * Uninitializes the idle system. + */ +void gaim_idle_uninit(void); + +/*@}*/ + +#ifdef __cplusplus +} +#endif + +#endif /* _GAIM_IDLE_H_ */ diff -r d10dda2777a9 -r b63ebf84c42b core/imgstore.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/imgstore.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,166 @@ +/** + * @file imgstore.h IM Image Store API + * @ingroup core + * + * 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 +#include +#include + +static GSList *imgstore = NULL; +static int nextid = 0; + +/** + * Stored image + * + * Represents a single IM image awaiting display and/or transmission. + * Now that this type is basicly private too, these two structs could + * probably be combined. + */ +struct _GaimStoredImage +{ + char *data; /**< The image data. */ + size_t size; /**< The image data's size. */ + char *filename; /**< The filename (for the UI) */ +}; + +typedef struct +{ + int id; + int refcount; + GaimStoredImage *img; +} GaimStoredImagePriv; + +/* private functions */ + +static GaimStoredImagePriv *gaim_imgstore_get_priv(int id) { + GSList *tmp = imgstore; + GaimStoredImagePriv *priv = NULL; + + g_return_val_if_fail(id > 0, NULL); + + while (tmp && !priv) { + GaimStoredImagePriv *tmp_priv = tmp->data; + + if (tmp_priv->id == id) + priv = tmp_priv; + + tmp = tmp->next; + } + + if (!priv) + gaim_debug(GAIM_DEBUG_ERROR, "imgstore", "failed to find image id %d\n", id); + + return priv; +} + +static void gaim_imgstore_free_priv(GaimStoredImagePriv *priv) { + GaimStoredImage *img = NULL; + + g_return_if_fail(priv != NULL); + + img = priv->img; + if (img) { + g_free(img->data); + g_free(img->filename); + g_free(img); + } + + gaim_debug(GAIM_DEBUG_INFO, "imgstore", "freed image id %d\n", priv->id); + + g_free(priv); + + imgstore = g_slist_remove(imgstore, priv); +} + +/* public functions */ + +int gaim_imgstore_add(const void *data, size_t size, const char *filename) { + GaimStoredImagePriv *priv; + GaimStoredImage *img; + + g_return_val_if_fail(data != NULL, 0); + g_return_val_if_fail(size > 0, 0); + + img = g_new0(GaimStoredImage, 1); + img->data = g_memdup(data, size); + img->size = size; + img->filename = g_strdup(filename); + + priv = g_new0(GaimStoredImagePriv, 1); + priv->id = ++nextid; + priv->refcount = 1; + priv->img = img; + + imgstore = g_slist_append(imgstore, priv); + gaim_debug(GAIM_DEBUG_INFO, "imgstore", "added image id %d\n", priv->id); + + return priv->id; +} + +GaimStoredImage *gaim_imgstore_get(int id) { + GaimStoredImagePriv *priv = gaim_imgstore_get_priv(id); + + g_return_val_if_fail(priv != NULL, NULL); + + gaim_debug(GAIM_DEBUG_INFO, "imgstore", "retrieved image id %d\n", priv->id); + + return priv->img; +} + +gpointer gaim_imgstore_get_data(GaimStoredImage *i) { + return i->data; +} + +size_t gaim_imgstore_get_size(GaimStoredImage *i) { + return i->size; +} + +const char *gaim_imgstore_get_filename(GaimStoredImage *i) { + return i->filename; +} + +void gaim_imgstore_ref(int id) { + GaimStoredImagePriv *priv = gaim_imgstore_get_priv(id); + + g_return_if_fail(priv != NULL); + + (priv->refcount)++; + + gaim_debug(GAIM_DEBUG_INFO, "imgstore", "referenced image id %d (count now %d)\n", priv->id, priv->refcount); +} + +void gaim_imgstore_unref(int id) { + GaimStoredImagePriv *priv = gaim_imgstore_get_priv(id); + + g_return_if_fail(priv != NULL); + g_return_if_fail(priv->refcount > 0); + + (priv->refcount)--; + + gaim_debug(GAIM_DEBUG_INFO, "imgstore", "unreferenced image id %d (count now %d)\n", priv->id, priv->refcount); + + if (priv->refcount == 0) + gaim_imgstore_free_priv(priv); +} diff -r d10dda2777a9 -r b63ebf84c42b core/imgstore.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/imgstore.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,111 @@ +/** + * @file imgstore.h IM Image Store API + * @ingroup core + * + * 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 + * + */ +#ifndef _GAIM_IMGSTORE_H_ +#define _GAIM_IMGSTORE_H_ + +struct _GaimStoredImage; +typedef struct _GaimStoredImage GaimStoredImage; + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Add an image to the store. The caller owns a reference + * to the image in the store, and must dereference the image + * with gaim_imgstore_unref for it to be freed. + * + * @param data Pointer to the image data. + * @param size Image data's size. + * @param filename Filename associated with image. + + * @return ID for the image. + */ +int gaim_imgstore_add(const void *data, size_t size, const char *filename); + +/** + * Retrieve an image from the store. The caller does not own a + * reference to the image. + * + * @param id The ID for the image. + * + * @return A pointer to the requested image, or NULL if it was not found. + */ +GaimStoredImage *gaim_imgstore_get(int id); + +/** + * Retrieves a pointer to the image's data. + * + * @param i The Image + * + * @return A pointer to the data, which must not + * be freed or modified. + */ +gpointer gaim_imgstore_get_data(GaimStoredImage *i); + +/** + * Retrieves the length of the image's data. + * + * @param i The Image + * + * @return The size of the data that the pointer returned by + * gaim_imgstore_get_data points to. + */ +size_t gaim_imgstore_get_size(GaimStoredImage *i); + +/** + * Retrieves a pointer to the image's filename. + * + * @param i The Image + * + * @return A pointer to the filename, which must not + * be freed or modified. + */ +const char *gaim_imgstore_get_filename(GaimStoredImage *i); + +/** + * Increment the reference count for an image in the store. The + * image will be removed from the store when the reference count + * is zero. + * + * @param id The ID for the image. + */ +void gaim_imgstore_ref(int id); + +/** + * Decrement the reference count for an image in the store. The + * image will be removed from the store when the reference count + * is zero. + * + * @param id The ID for the image. + */ +void gaim_imgstore_unref(int id); + +#ifdef __cplusplus +} +#endif + +#endif /* _GAIM_IMGSTORE_H_ */ diff -r d10dda2777a9 -r b63ebf84c42b core/internal.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/internal.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,184 @@ +/** + * @file internal.h Internal definitions and includes + * @ingroup core + * + * 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 + */ +#ifndef _GAIM_INTERNAL_H_ +#define _GAIM_INTERNAL_H_ + +#ifdef HAVE_CONFIG_H +# include +#endif + +/* + * If we're using NLS, make sure gettext works. If not, then define + * dummy macros in place of the normal gettext macros. + * + * Also, the perl XS config.h file sometimes defines _ So we need to + * make sure _ isn't already defined before trying to define it. + * + * The Singular/Plural/Number ngettext dummy definition below was + * taken from an email to the texinfo mailing list by Manuel Guerrero. + * Thank you Manuel, and thank you Alex's good friend Google. + */ +#ifdef ENABLE_NLS +# include +# include +# define _(x) ((const char *)gettext(x)) +# ifdef gettext_noop +# define N_(String) gettext_noop (String) +# else +# define N_(String) (String) +# endif +#else +# include +# define N_(String) (String) +# ifndef _ +# define _(x) ((const char *)x) +# endif +# define ngettext(Singular, Plural, Number) ((Number == 1) ? ((const char *)Singular) : ((const char *)Plural)) +#endif + +#ifdef HAVE_ENDIAN_H +# include +#endif + +#define MSG_LEN 2048 +/* The above should normally be the same as BUF_LEN, + * but just so we're explicitly asking for the max message + * length. */ +#define BUF_LEN MSG_LEN +#define BUF_LONG BUF_LEN * 2 + +#include +#include +#include +#ifndef _WIN32 +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_ICONV +#include +#endif + +#ifdef HAVE_LANGINFO_CODESET +#include +#endif + +#ifdef GAIM_PLUGINS +# include +# ifndef _WIN32 +# include +# endif +#endif + +#ifndef _WIN32 +# include +# include +# include +# include +# include +# include +# include +# include +#endif + +#ifndef MAXPATHLEN +# define MAXPATHLEN 1024 +#endif + +#ifndef HOST_NAME_MAX +# define HOST_NAME_MAX 255 +#endif + +#define PATHSIZE 1024 + +#include +#if GLIB_CHECK_VERSION(2,6,0) +# include +#endif + +#ifdef _WIN32 +#include "win32dep.h" +#endif + +#if !GLIB_CHECK_VERSION(2,6,0) +# define g_freopen freopen +# define g_fopen fopen +# define g_rmdir rmdir +# define g_remove remove +# define g_unlink unlink +# define g_lstat lstat +# define g_stat stat +# define g_mkdir mkdir +# define g_rename rename +# define g_open open +#endif + +#if !GLIB_CHECK_VERSION(2,10,0) +# define g_slice_new(type) g_new(type, 1) +# define g_slice_new0(type) g_new0(type, 1) +# define g_slice_free(type, mem) g_free(mem) +#endif + +/* ugly ugly ugly */ +/* This is a workaround for the fact that G_GINT64_MODIFIER and G_GSIZE_FORMAT + * are only defined in Glib >= 2.4 */ +#ifndef G_GINT64_MODIFIER +# if GLIB_SIZEOF_LONG == 8 +# define G_GINT64_MODIFIER "l" +# else +# define G_GINT64_MODIFIER "ll" +# endif +#endif + +#ifndef G_GSIZE_FORMAT +# if GLIB_SIZEOF_LONG == 8 +# define G_GSIZE_FORMAT "lu" +# else +# define G_GSIZE_FORMAT "u" +# endif +#endif + +/* Safer ways to work with static buffers. When using non-static + * buffers, either use g_strdup_* functions (preferred) or use + * g_strlcpy/g_strlcpy directly. */ +#define gaim_strlcpy(dest, src) g_strlcpy(dest, src, sizeof(dest)) +#define gaim_strlcat(dest, src) g_strlcat(dest, src, sizeof(dest)) + +#define GAIM_WEBSITE "http://gaim.sourceforge.net/" + +#ifndef _WIN32 +/* Everything needs to include this, because + * everything gets the autoconf macros */ +#include "prefix.h" +#endif /* _WIN32 */ + +#endif /* _GAIM_INTERNAL_H_ */ diff -r d10dda2777a9 -r b63ebf84c42b core/log.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/log.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,1732 @@ +/** + * @file log.c Logging API + * @ingroup core + * + * 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 "account.h" +#include "dbus-maybe.h" +#include "debug.h" +#include "internal.h" +#include "log.h" +#include "prefs.h" +#include "util.h" +#include "stringref.h" + +static GSList *loggers = NULL; + +static GaimLogLogger *html_logger; +static GaimLogLogger *txt_logger; +static GaimLogLogger *old_logger; + +struct _gaim_logsize_user { + char *name; + GaimAccount *account; +}; +static GHashTable *logsize_users = NULL; + +static void log_get_log_sets_common(GHashTable *sets); + +static gsize html_logger_write(GaimLog *log, GaimMessageFlags type, + const char *from, time_t time, const char *message); +static void html_logger_finalize(GaimLog *log); +static GList *html_logger_list(GaimLogType type, const char *sn, GaimAccount *account); +static GList *html_logger_list_syslog(GaimAccount *account); +static char *html_logger_read(GaimLog *log, GaimLogReadFlags *flags); +static int html_logger_total_size(GaimLogType type, const char *name, GaimAccount *account); + +static GList *old_logger_list(GaimLogType type, const char *sn, GaimAccount *account); +static int old_logger_total_size(GaimLogType type, const char *name, GaimAccount *account); +static char * old_logger_read (GaimLog *log, GaimLogReadFlags *flags); +static int old_logger_size (GaimLog *log); +static void old_logger_get_log_sets(GaimLogSetCallback cb, GHashTable *sets); +static void old_logger_finalize(GaimLog *log); + +static gsize txt_logger_write(GaimLog *log, + GaimMessageFlags type, + const char *from, time_t time, const char *message); +static void txt_logger_finalize(GaimLog *log); +static GList *txt_logger_list(GaimLogType type, const char *sn, GaimAccount *account); +static GList *txt_logger_list_syslog(GaimAccount *account); +static char *txt_logger_read(GaimLog *log, GaimLogReadFlags *flags); +static int txt_logger_total_size(GaimLogType type, const char *name, GaimAccount *account); + +/************************************************************************** + * PUBLIC LOGGING FUNCTIONS *********************************************** + **************************************************************************/ + +GaimLog *gaim_log_new(GaimLogType type, const char *name, GaimAccount *account, + GaimConversation *conv, time_t time, const struct tm *tm) +{ + GaimLog *log; + + /* IMPORTANT: Make sure to initialize all the members of GaimLog */ + log = g_slice_new(GaimLog); + GAIM_DBUS_REGISTER_POINTER(log, GaimLog); + + log->type = type; + log->name = g_strdup(gaim_normalize(account, name)); + log->account = account; + log->conv = conv; + log->time = time; + log->logger = gaim_log_logger_get(); + log->logger_data = NULL; + + if (tm == NULL) + log->tm = NULL; + else + { + /* There's no need to zero this as we immediately do a direct copy. */ + log->tm = g_slice_new(struct tm); + + *(log->tm) = *tm; + +#ifdef HAVE_STRUCT_TM_TM_ZONE + /* XXX: This is so wrong... */ + if (log->tm->tm_zone != NULL) + { + char *tmp = g_locale_from_utf8(log->tm->tm_zone, -1, NULL, NULL, NULL); + if (tmp != NULL) + log->tm->tm_zone = tmp; + else + /* Just shove the UTF-8 bytes in and hope... */ + log->tm->tm_zone = g_strdup(log->tm->tm_zone); + } +#endif + } + + if (log->logger && log->logger->create) + log->logger->create(log); + return log; +} + +void gaim_log_free(GaimLog *log) +{ + g_return_if_fail(log); + if (log->logger && log->logger->finalize) + log->logger->finalize(log); + g_free(log->name); + + if (log->tm != NULL) + { +#ifdef HAVE_STRUCT_TM_TM_ZONE + /* XXX: This is so wrong... */ + g_free((char *)log->tm->tm_zone); +#endif + g_slice_free(struct tm, log->tm); + } + + GAIM_DBUS_UNREGISTER_POINTER(log); + g_slice_free(GaimLog, log); +} + +void gaim_log_write(GaimLog *log, GaimMessageFlags type, + const char *from, time_t time, const char *message) +{ + struct _gaim_logsize_user *lu; + gsize written, total = 0; + gpointer ptrsize; + + g_return_if_fail(log); + g_return_if_fail(log->logger); + g_return_if_fail(log->logger->write); + + written = (log->logger->write)(log, type, from, time, message); + + lu = g_new(struct _gaim_logsize_user, 1); + + lu->name = g_strdup(gaim_normalize(log->account, log->name)); + lu->account = log->account; + + if(g_hash_table_lookup_extended(logsize_users, lu, NULL, &ptrsize)) { + total = GPOINTER_TO_INT(ptrsize); + total += written; + g_hash_table_replace(logsize_users, lu, GINT_TO_POINTER(total)); + } else { + g_free(lu->name); + g_free(lu); + } + +} + +char *gaim_log_read(GaimLog *log, GaimLogReadFlags *flags) +{ + GaimLogReadFlags mflags; + g_return_val_if_fail(log && log->logger, NULL); + if (log->logger->read) { + char *ret = (log->logger->read)(log, flags ? flags : &mflags); + gaim_str_strip_char(ret, '\r'); + return ret; + } + return g_strdup(_("The logger has no read function")); +} + +int gaim_log_get_size(GaimLog *log) +{ + g_return_val_if_fail(log && log->logger, 0); + + if (log->logger->size) + return log->logger->size(log); + return 0; +} + +static guint _gaim_logsize_user_hash(struct _gaim_logsize_user *lu) +{ + return g_str_hash(lu->name); +} + +static guint _gaim_logsize_user_equal(struct _gaim_logsize_user *lu1, + struct _gaim_logsize_user *lu2) +{ + return (lu1->account == lu2->account && (!strcmp(lu1->name, lu2->name))); +} + +static void _gaim_logsize_user_free_key(struct _gaim_logsize_user *lu) +{ + g_free(lu->name); + g_free(lu); +} + +int gaim_log_get_total_size(GaimLogType type, const char *name, GaimAccount *account) +{ + gpointer ptrsize; + int size = 0; + GSList *n; + struct _gaim_logsize_user *lu; + + lu = g_new(struct _gaim_logsize_user, 1); + lu->name = g_strdup(gaim_normalize(account, name)); + lu->account = account; + + if(g_hash_table_lookup_extended(logsize_users, lu, NULL, &ptrsize)) { + size = GPOINTER_TO_INT(ptrsize); + g_free(lu->name); + g_free(lu); + } else { + for (n = loggers; n; n = n->next) { + GaimLogLogger *logger = n->data; + + if(logger->total_size){ + size += (logger->total_size)(type, name, account); + } else if(logger->list) { + GList *logs = (logger->list)(type, name, account); + int this_size = 0; + + while (logs) { + GaimLog *log = (GaimLog*)(logs->data); + this_size += gaim_log_get_size(log); + gaim_log_free(log); + logs = g_list_delete_link(logs, logs); + } + + size += this_size; + } + } + + g_hash_table_replace(logsize_users, lu, GINT_TO_POINTER(size)); + } + return size; +} + +char * +gaim_log_get_log_dir(GaimLogType type, const char *name, GaimAccount *account) +{ + GaimPlugin *prpl; + GaimPluginProtocolInfo *prpl_info; + const char *prpl_name; + char *acct_name; + const char *target; + char *dir; + + prpl = gaim_find_prpl(gaim_account_get_protocol_id(account)); + if (!prpl) + return NULL; + prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(prpl); + prpl_name = prpl_info->list_icon(account, NULL); + + acct_name = g_strdup(gaim_escape_filename(gaim_normalize(account, + gaim_account_get_username(account)))); + + if (type == GAIM_LOG_CHAT) { + char *temp = g_strdup_printf("%s.chat", gaim_normalize(account, name)); + target = gaim_escape_filename(temp); + g_free(temp); + } else if(type == GAIM_LOG_SYSTEM) { + target = ".system"; + } else { + target = gaim_escape_filename(gaim_normalize(account, name)); + } + + dir = g_build_filename(gaim_user_dir(), "logs", prpl_name, acct_name, target, NULL); + + g_free(acct_name); + + return dir; +} + +/**************************************************************************** + * LOGGER FUNCTIONS ********************************************************* + ****************************************************************************/ + +static GaimLogLogger *current_logger = NULL; + +static void logger_pref_cb(const char *name, GaimPrefType type, + gconstpointer value, gpointer data) +{ + GaimLogLogger *logger; + GSList *l = loggers; + while (l) { + logger = l->data; + if (!strcmp(logger->id, value)) { + gaim_log_logger_set(logger); + return; + } + l = l->next; + } + gaim_log_logger_set(txt_logger); +} + + +GaimLogLogger *gaim_log_logger_new(const char *id, const char *name, int functions, ...) +{ +#if 0 + void(*create)(GaimLog *), + gsize(*write)(GaimLog *, GaimMessageFlags, const char *, time_t, const char *), + void(*finalize)(GaimLog *), + GList*(*list)(GaimLogType type, const char*, GaimAccount*), + char*(*read)(GaimLog*, GaimLogReadFlags*), + int(*size)(GaimLog*), + int(*total_size)(GaimLogType type, const char *name, GaimAccount *account), + GList*(*list_syslog)(GaimAccount *account), + void(*get_log_sets)(GaimLogSetCallback cb, GHashTable *sets)) +{ +#endif + GaimLogLogger *logger; + va_list args; + + g_return_val_if_fail(id != NULL, NULL); + g_return_val_if_fail(name != NULL, NULL); + g_return_val_if_fail(functions >= 1, NULL); + + logger = g_new0(GaimLogLogger, 1); + logger->id = g_strdup(id); + logger->name = g_strdup(name); + + va_start(args, functions); + + if (functions >= 1) + logger->create = va_arg(args, void *); + if (functions >= 2) + logger->write = va_arg(args, void *); + if (functions >= 3) + logger->finalize = va_arg(args, void *); + if (functions >= 4) + logger->list = va_arg(args, void *); + if (functions >= 5) + logger->read = va_arg(args, void *); + if (functions >= 6) + logger->size = va_arg(args, void *); + if (functions >= 7) + logger->total_size = va_arg(args, void *); + if (functions >= 8) + logger->list_syslog = va_arg(args, void *); + if (functions >= 9) + logger->get_log_sets = va_arg(args, void *); + + if (functions > 9) + gaim_debug_info("log", "Dropping new functions for logger: %s (%s)\n", name, id); + + va_end(args); + + return logger; +} + +void gaim_log_logger_free(GaimLogLogger *logger) +{ + g_free(logger->name); + g_free(logger->id); + g_free(logger); +} + +void gaim_log_logger_add (GaimLogLogger *logger) +{ + g_return_if_fail(logger); + if (g_slist_find(loggers, logger)) + return; + loggers = g_slist_append(loggers, logger); +} + +void gaim_log_logger_remove (GaimLogLogger *logger) +{ + g_return_if_fail(logger); + loggers = g_slist_remove(loggers, logger); +} + +void gaim_log_logger_set (GaimLogLogger *logger) +{ + g_return_if_fail(logger); + current_logger = logger; +} + +GaimLogLogger *gaim_log_logger_get() +{ + return current_logger; +} + +GList *gaim_log_logger_get_options(void) +{ + GSList *n; + GList *list = NULL; + GaimLogLogger *data; + + for (n = loggers; n; n = n->next) { + data = n->data; + if (!data->write) + continue; + list = g_list_append(list, data->name); + list = g_list_append(list, data->id); + } + + return list; +} + +gint gaim_log_compare(gconstpointer y, gconstpointer z) +{ + const GaimLog *a = y; + const GaimLog *b = z; + + return b->time - a->time; +} + +GList *gaim_log_get_logs(GaimLogType type, const char *name, GaimAccount *account) +{ + GList *logs = NULL; + GSList *n; + for (n = loggers; n; n = n->next) { + GaimLogLogger *logger = n->data; + if (!logger->list) + continue; + logs = g_list_concat(logger->list(type, name, account), logs); + } + + return g_list_sort(logs, gaim_log_compare); +} + +gint gaim_log_set_compare(gconstpointer y, gconstpointer z) +{ + const GaimLogSet *a = y; + const GaimLogSet *b = z; + gint ret = 0; + + /* This logic seems weird at first... + * If either account is NULL, we pretend the accounts are + * equal. This allows us to detect duplicates that will + * exist if one logger knows the account and another + * doesn't. */ + if (a->account != NULL && b->account != NULL) { + ret = strcmp(gaim_account_get_username(a->account), gaim_account_get_username(b->account)); + if (ret != 0) + return ret; + } + + ret = strcmp(a->normalized_name, b->normalized_name); + if (ret != 0) + return ret; + + return (gint)b->type - (gint)a->type; +} + +static guint +log_set_hash(gconstpointer key) +{ + const GaimLogSet *set = key; + + /* The account isn't hashed because we need GaimLogSets with NULL accounts + * to be found when we search by a GaimLogSet that has a non-NULL account + * but the same type and name. */ + return g_int_hash((gint *)&set->type) + g_str_hash(set->name); +} + +static gboolean +log_set_equal(gconstpointer a, gconstpointer b) +{ + /* I realize that the choices made for GList and GHashTable + * make sense for those data types, but I wish the comparison + * functions were compatible. */ + return !gaim_log_set_compare(a, b); +} + +static void +log_add_log_set_to_hash(GHashTable *sets, GaimLogSet *set) +{ + GaimLogSet *existing_set = g_hash_table_lookup(sets, set); + + if (existing_set == NULL) + g_hash_table_insert(sets, set, set); + else if (existing_set->account == NULL && set->account != NULL) + g_hash_table_replace(sets, set, set); + else + gaim_log_set_free(set); +} + +GHashTable *gaim_log_get_log_sets(void) +{ + GSList *n; + GHashTable *sets = g_hash_table_new_full(log_set_hash, log_set_equal, + (GDestroyNotify)gaim_log_set_free, NULL); + + /* Get the log sets from all the loggers. */ + for (n = loggers; n; n = n->next) { + GaimLogLogger *logger = n->data; + + if (!logger->get_log_sets) + continue; + + logger->get_log_sets(log_add_log_set_to_hash, sets); + } + + log_get_log_sets_common(sets); + + /* Return the GHashTable of unique GaimLogSets. */ + return sets; +} + +void gaim_log_set_free(GaimLogSet *set) +{ + g_return_if_fail(set != NULL); + + g_free(set->name); + if (set->normalized_name != set->name) + g_free(set->normalized_name); + + g_slice_free(GaimLogSet, set); +} + +GList *gaim_log_get_system_logs(GaimAccount *account) +{ + GList *logs = NULL; + GSList *n; + for (n = loggers; n; n = n->next) { + GaimLogLogger *logger = n->data; + if (!logger->list_syslog) + continue; + logs = g_list_concat(logger->list_syslog(account), logs); + } + + return g_list_sort(logs, gaim_log_compare); +} + +/**************************************************************************** + * LOG SUBSYSTEM ************************************************************ + ****************************************************************************/ + +void * +gaim_log_get_handle(void) +{ + static int handle; + + return &handle; +} + +void gaim_log_init(void) +{ + void *handle = gaim_log_get_handle(); + + gaim_prefs_add_none("/core/logging"); + gaim_prefs_add_bool("/core/logging/log_ims", FALSE); + gaim_prefs_add_bool("/core/logging/log_chats", FALSE); + gaim_prefs_add_bool("/core/logging/log_system", FALSE); + + gaim_prefs_add_string("/core/logging/format", "txt"); + + html_logger = gaim_log_logger_new("html", _("HTML"), 8, + NULL, + html_logger_write, + html_logger_finalize, + html_logger_list, + html_logger_read, + gaim_log_common_sizer, + html_logger_total_size, + html_logger_list_syslog); + gaim_log_logger_add(html_logger); + + txt_logger = gaim_log_logger_new("txt", _("Plain text"), 8, + NULL, + txt_logger_write, + txt_logger_finalize, + txt_logger_list, + txt_logger_read, + gaim_log_common_sizer, + txt_logger_total_size, + txt_logger_list_syslog); + gaim_log_logger_add(txt_logger); + + old_logger = gaim_log_logger_new("old", _("Old Gaim"), 9, + NULL, + NULL, + old_logger_finalize, + old_logger_list, + old_logger_read, + old_logger_size, + old_logger_total_size, + NULL, + old_logger_get_log_sets); + gaim_log_logger_add(old_logger); + + gaim_signal_register(handle, "log-timestamp", +#if SIZEOF_TIME_T == 4 + gaim_marshal_POINTER__POINTER_INT, +#elif SIZEOF_TIME_T == 8 + gaim_marshal_POINTER__POINTER_INT64, +#else +#error Unknown size of time_t +#endif + gaim_value_new(GAIM_TYPE_POINTER), 2, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_LOG), +#if SIZEOF_TIME_T == 4 + gaim_value_new(GAIM_TYPE_INT)); +#elif SIZEOF_TIME_T == 8 + gaim_value_new(GAIM_TYPE_INT64)); +#else +# error Unknown size of time_t +#endif + + gaim_prefs_connect_callback(NULL, "/core/logging/format", + logger_pref_cb, NULL); + gaim_prefs_trigger_callback("/core/logging/format"); + + logsize_users = g_hash_table_new_full((GHashFunc)_gaim_logsize_user_hash, + (GEqualFunc)_gaim_logsize_user_equal, + (GDestroyNotify)_gaim_logsize_user_free_key, NULL); +} + +void +gaim_log_uninit(void) +{ + gaim_signals_unregister_by_instance(gaim_log_get_handle()); +} + +/**************************************************************************** + * LOGGERS ****************************************************************** + ****************************************************************************/ + +static char *log_get_timestamp(GaimLog *log, time_t when) +{ + char *date; + struct tm tm; + + date = gaim_signal_emit_return_1(gaim_log_get_handle(), + "log-timestamp", + log, when); + if (date != NULL) + return date; + + tm = *(localtime(&when)); + if (log->type == GAIM_LOG_SYSTEM || time(NULL) > when + 20*60) + return g_strdup(gaim_date_format_long(&tm)); + else + return g_strdup(gaim_time_format(&tm)); +} + +void gaim_log_common_writer(GaimLog *log, const char *ext) +{ + GaimLogCommonLoggerData *data = log->logger_data; + + if (data == NULL) + { + /* This log is new */ + char *dir; + struct tm *tm; + const char *tz; + const char *date; + char *filename; + char *path; + + dir = gaim_log_get_log_dir(log->type, log->name, log->account); + if (dir == NULL) + return; + + gaim_build_dir (dir, S_IRUSR | S_IWUSR | S_IXUSR); + + tm = localtime(&log->time); + tz = gaim_escape_filename(gaim_utf8_strftime("%Z", tm)); + date = gaim_utf8_strftime("%Y-%m-%d.%H%M%S%z", tm); + + filename = g_strdup_printf("%s%s%s", date, tz, ext ? ext : ""); + + path = g_build_filename(dir, filename, NULL); + g_free(dir); + g_free(filename); + + log->logger_data = data = g_slice_new0(GaimLogCommonLoggerData); + + data->file = g_fopen(path, "a"); + if (data->file == NULL) + { + gaim_debug(GAIM_DEBUG_ERROR, "log", + "Could not create log file %s\n", path); + + if (log->conv != NULL) + gaim_conversation_write(log->conv, NULL, _("Logging of this conversation failed."), + GAIM_MESSAGE_ERROR, time(NULL)); + + g_free(path); + return; + } + g_free(path); + } +} + +GList *gaim_log_common_lister(GaimLogType type, const char *name, GaimAccount *account, const char *ext, GaimLogLogger *logger) +{ + GDir *dir; + GList *list = NULL; + const char *filename; + char *path; + + if(!account) + return NULL; + + path = gaim_log_get_log_dir(type, name, account); + if (path == NULL) + return NULL; + + if (!(dir = g_dir_open(path, 0, NULL))) + { + g_free(path); + return NULL; + } + + while ((filename = g_dir_read_name(dir))) + { + if (gaim_str_has_suffix(filename, ext) && + strlen(filename) >= (17 + strlen(ext))) + { + GaimLog *log; + GaimLogCommonLoggerData *data; + struct tm tm; +#if defined (HAVE_TM_GMTOFF) && defined (HAVE_STRUCT_TM_TM_ZONE) + long tz_off; + const char *rest; + time_t stamp = gaim_str_to_time(gaim_unescape_filename(filename), FALSE, &tm, &tz_off, &rest); + char *end; + + /* As zero is a valid offset, GAIM_NO_TZ_OFF means no offset was + * provided. See util.h. Yes, it's kinda ugly. */ + if (tz_off != GAIM_NO_TZ_OFF) + tm.tm_gmtoff = tz_off - tm.tm_gmtoff; + + if (rest == NULL || (end = strchr(rest, '.')) == NULL || strchr(rest, ' ') != NULL) + { + log = gaim_log_new(type, name, account, NULL, stamp, NULL); + } + else + { + char *tmp = g_strndup(rest, end - rest); + tm.tm_zone = tmp; + log = gaim_log_new(type, name, account, NULL, stamp, &tm); + g_free(tmp); + } +#else + time_t stamp = gaim_str_to_time(filename, FALSE, &tm, NULL, NULL); + + log = gaim_log_new(type, name, account, NULL, stamp, &tm); +#endif + + log->logger = logger; + log->logger_data = data = g_slice_new0(GaimLogCommonLoggerData); + + data->path = g_build_filename(path, filename, NULL); + list = g_list_prepend(list, log); + } + } + g_dir_close(dir); + g_free(path); + return list; +} + +int gaim_log_common_total_sizer(GaimLogType type, const char *name, GaimAccount *account, const char *ext) +{ + GDir *dir; + int size = 0; + const char *filename; + char *path; + + if(!account) + return 0; + + path = gaim_log_get_log_dir(type, name, account); + if (path == NULL) + return 0; + + if (!(dir = g_dir_open(path, 0, NULL))) + { + g_free(path); + return 0; + } + + while ((filename = g_dir_read_name(dir))) + { + if (gaim_str_has_suffix(filename, ext) && + strlen(filename) >= (17 + strlen(ext))) + { + char *tmp = g_build_filename(path, filename, NULL); + struct stat st; + if (g_stat(tmp, &st)) + { + gaim_debug_error("log", "Error stating log file: %s\n", tmp); + g_free(tmp); + continue; + } + g_free(tmp); + size += st.st_size; + } + } + g_dir_close(dir); + g_free(path); + return size; +} + +int gaim_log_common_sizer(GaimLog *log) +{ + struct stat st; + GaimLogCommonLoggerData *data = log->logger_data; + + if (!data->path || g_stat(data->path, &st)) + st.st_size = 0; + + return st.st_size; +} + +/* This will build log sets for all loggers that use the common logger + * functions because they use the same directory structure. */ +static void log_get_log_sets_common(GHashTable *sets) +{ + gchar *log_path = g_build_filename(gaim_user_dir(), "logs", NULL); + GDir *log_dir = g_dir_open(log_path, 0, NULL); + const gchar *protocol; + + if (log_dir == NULL) { + g_free(log_path); + return; + } + + while ((protocol = g_dir_read_name(log_dir)) != NULL) { + gchar *protocol_path = g_build_filename(log_path, protocol, NULL); + GDir *protocol_dir; + const gchar *username; + gchar *protocol_unescaped; + GList *account_iter; + GList *accounts = NULL; + + if ((protocol_dir = g_dir_open(protocol_path, 0, NULL)) == NULL) { + g_free(protocol_path); + continue; + } + + /* Using g_strdup() to cover the one-in-a-million chance that a + * prpl's list_icon function uses gaim_unescape_filename(). */ + protocol_unescaped = g_strdup(gaim_unescape_filename(protocol)); + + /* Find all the accounts for protocol. */ + for (account_iter = gaim_accounts_get_all() ; account_iter != NULL ; account_iter = account_iter->next) { + GaimPlugin *prpl; + GaimPluginProtocolInfo *prpl_info; + + prpl = gaim_find_prpl(gaim_account_get_protocol_id((GaimAccount *)account_iter->data)); + if (!prpl) + continue; + prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(prpl); + + if (!strcmp(protocol_unescaped, prpl_info->list_icon((GaimAccount *)account_iter->data, NULL))) + accounts = g_list_prepend(accounts, account_iter->data); + } + g_free(protocol_unescaped); + + while ((username = g_dir_read_name(protocol_dir)) != NULL) { + gchar *username_path = g_build_filename(protocol_path, username, NULL); + GDir *username_dir; + const gchar *username_unescaped; + GaimAccount *account = NULL; + gchar *name; + + if ((username_dir = g_dir_open(username_path, 0, NULL)) == NULL) { + g_free(username_path); + continue; + } + + /* Find the account for username in the list of accounts for protocol. */ + username_unescaped = gaim_unescape_filename(username); + for (account_iter = g_list_first(accounts) ; account_iter != NULL ; account_iter = account_iter->next) { + if (!strcmp(((GaimAccount *)account_iter->data)->username, username_unescaped)) { + account = account_iter->data; + break; + } + } + + /* Don't worry about the cast, name will point to dynamically allocated memory shortly. */ + while ((name = (gchar *)g_dir_read_name(username_dir)) != NULL) { + size_t len; + GaimLogSet *set; + + /* IMPORTANT: Always initialize all members of GaimLogSet */ + set = g_slice_new(GaimLogSet); + + /* Unescape the filename. */ + name = g_strdup(gaim_unescape_filename(name)); + + /* Get the (possibly new) length of name. */ + len = strlen(name); + + set->type = GAIM_LOG_IM; + set->name = name; + set->account = account; + /* set->buddy is always set below */ + set->normalized_name = g_strdup(gaim_normalize(account, name)); + + /* Chat for .chat or .system at the end of the name to determine the type. */ + if (len > 7) { + gchar *tmp = &name[len - 7]; + if (!strcmp(tmp, ".system")) { + set->type = GAIM_LOG_SYSTEM; + *tmp = '\0'; + } + } + if (len > 5) { + gchar *tmp = &name[len - 5]; + if (!strcmp(tmp, ".chat")) { + set->type = GAIM_LOG_CHAT; + *tmp = '\0'; + } + } + + /* Determine if this (account, name) combination exists as a buddy. */ + if (account != NULL) + set->buddy = (gaim_find_buddy(account, name) != NULL); + else + set->buddy = FALSE; + + log_add_log_set_to_hash(sets, set); + } + g_free(username_path); + g_dir_close(username_dir); + } + g_free(protocol_path); + g_dir_close(protocol_dir); + } + g_free(log_path); + g_dir_close(log_dir); +} + +#if 0 /* Maybe some other time. */ +/**************** + ** XML LOGGER ** + ****************/ + +static const char *str_from_msg_type (GaimMessageFlags type) +{ + + return ""; + +} + +static void xml_logger_write(GaimLog *log, + GaimMessageFlags type, + const char *from, time_t time, const char *message) +{ + char *xhtml = NULL; + + if (!log->logger_data) { + /* This log is new. We could use the loggers 'new' function, but + * creating a new file there would result in empty files in the case + * that you open a convo with someone, but don't say anything. + */ + struct tm *tm; + const char *tz; + const char *date; + char *dir = gaim_log_get_log_dir(log->type, log->name, log->account); + char *name; + char *filename; + + if (dir == NULL) + return; + + tm = localtime(&log->time); + tz = gaim_escape_filename(gaim_utf8_strftime("%Z", tm); + date = gaim_utf8_strftime("%Y-%m-%d.%H%M%S%z", tm); + + name = g_strdup_printf("%s%s%s", date, tz, ext ? ext : ""); + + gaim_build_dir (dir, S_IRUSR | S_IWUSR | S_IXUSR); + + filename = g_build_filename(dir, name, NULL); + g_free(dir); + g_free(name); + + log->logger_data = g_fopen(filename, "a"); + if (!log->logger_data) { + gaim_debug(GAIM_DEBUG_ERROR, "log", "Could not create log file %s\n", filename); + g_free(filename); + return; + } + g_free(filename); + fprintf(log->logger_data, "\n" + "\n"); + + date = gaim_utf8_strftime("%Y-%m-%d %H:%M:%S", localtime(&log->time)); + fprintf(log->logger_data, "\n", + date, log->name, prpl); + } + + /* if we can't write to the file, give up before we hurt ourselves */ + if(!data->file) + return; + + date = log_get_timestamp(log, time); + + gaim_markup_html_to_xhtml(message, &xhtml, NULL); + if (from) + fprintf(log->logger_data, "%s\n", + str_from_msg_type(type), + type & GAIM_MESSAGE_SEND ? "direction='sent'" : + type & GAIM_MESSAGE_RECV ? "direction='received'" : "", + from, date, xhtml); + else + fprintf(log->logger_data, "%s\n", + str_from_msg_type(type), + type & GAIM_MESSAGE_SEND ? "direction='sent'" : + type & GAIM_MESSAGE_RECV ? "direction='received'" : "", + date, xhtml): + fflush(log->logger_data); + g_free(date); + g_free(xhtml); +} + + static void xml_logger_finalize(GaimLog *log) +{ + if (log->logger_data) { + fprintf(log->logger_data, "\n"); + fclose(log->logger_data); + log->logger_data = NULL; + } +} + +static GList *xml_logger_list(GaimLogType type, const char *sn, GaimAccount *account) +{ + return gaim_log_common_lister(type, sn, account, ".xml", &xml_logger); +} + +static GaimLogLogger xml_logger = { + N_("XML"), "xml", + NULL, + xml_logger_write, + xml_logger_finalize, + xml_logger_list, + NULL, + NULL, + NULL +}; +#endif + +/**************************** + ** HTML LOGGER ************* + ****************************/ + +static gsize html_logger_write(GaimLog *log, GaimMessageFlags type, + const char *from, time_t time, const char *message) +{ + char *msg_fixed; + char *date; + char *header; + GaimPlugin *plugin = gaim_find_prpl(gaim_account_get_protocol_id(log->account)); + GaimLogCommonLoggerData *data = log->logger_data; + gsize written = 0; + + if(!data) { + const char *prpl = + GAIM_PLUGIN_PROTOCOL_INFO(plugin)->list_icon(log->account, NULL); + const char *date; + gaim_log_common_writer(log, ".html"); + + data = log->logger_data; + + /* if we can't write to the file, give up before we hurt ourselves */ + if(!data->file) + return 0; + + date = gaim_date_format_full(localtime(&log->time)); + + written += fprintf(data->file, ""); + written += fprintf(data->file, ""); + written += fprintf(data->file, ""); + if (log->type == GAIM_LOG_SYSTEM) + header = g_strdup_printf("System log for account %s (%s) connected at %s", + gaim_account_get_username(log->account), prpl, date); + else + header = g_strdup_printf("Conversation with %s at %s on %s (%s)", + log->name, date, gaim_account_get_username(log->account), prpl); + + written += fprintf(data->file, "%s", header); + written += fprintf(data->file, ""); + written += fprintf(data->file, "

%s

\n", header); + g_free(header); + } + + /* if we can't write to the file, give up before we hurt ourselves */ + if(!data->file) + return 0; + + gaim_markup_html_to_xhtml(message, &msg_fixed, NULL); + date = log_get_timestamp(log, time); + + if(log->type == GAIM_LOG_SYSTEM){ + written += fprintf(data->file, "---- %s @ %s ----
\n", msg_fixed, date); + } else { + if (type & GAIM_MESSAGE_SYSTEM) + written += fprintf(data->file, "(%s) %s
\n", date, msg_fixed); + else if (type & GAIM_MESSAGE_ERROR) + written += fprintf(data->file, "(%s) %s
\n", date, msg_fixed); + else if (type & GAIM_MESSAGE_WHISPER) + written += fprintf(data->file, "(%s) %s: %s
\n", + date, from, msg_fixed); + else if (type & GAIM_MESSAGE_AUTO_RESP) { + if (type & GAIM_MESSAGE_SEND) + written += fprintf(data->file, _("(%s) %s <AUTO-REPLY>: %s
\n"), date, from, msg_fixed); + else if (type & GAIM_MESSAGE_RECV) + written += fprintf(data->file, _("(%s) %s <AUTO-REPLY>: %s
\n"), date, from, msg_fixed); + } else if (type & GAIM_MESSAGE_RECV) { + if(gaim_message_meify(msg_fixed, -1)) + written += fprintf(data->file, "(%s) ***%s %s
\n", + date, from, msg_fixed); + else + written += fprintf(data->file, "(%s) %s: %s
\n", + date, from, msg_fixed); + } else if (type & GAIM_MESSAGE_SEND) { + if(gaim_message_meify(msg_fixed, -1)) + written += fprintf(data->file, "(%s) ***%s %s
\n", + date, from, msg_fixed); + else + written += fprintf(data->file, "(%s) %s: %s
\n", + date, from, msg_fixed); + } else { + gaim_debug_error("log", "Unhandled message type."); + written += fprintf(data->file, "(%s) %s: %s
\n", + date, from, msg_fixed); + } + } + g_free(date); + g_free(msg_fixed); + fflush(data->file); + + return written; +} + +static void html_logger_finalize(GaimLog *log) +{ + GaimLogCommonLoggerData *data = log->logger_data; + if (data) { + if(data->file) { + fprintf(data->file, "\n"); + fclose(data->file); + } + g_free(data->path); + + g_slice_free(GaimLogCommonLoggerData, data); + } +} + +static GList *html_logger_list(GaimLogType type, const char *sn, GaimAccount *account) +{ + return gaim_log_common_lister(type, sn, account, ".html", html_logger); +} + +static GList *html_logger_list_syslog(GaimAccount *account) +{ + return gaim_log_common_lister(GAIM_LOG_SYSTEM, ".system", account, ".html", html_logger); +} + +static char *html_logger_read(GaimLog *log, GaimLogReadFlags *flags) +{ + char *read; + GaimLogCommonLoggerData *data = log->logger_data; + *flags = GAIM_LOG_READ_NO_NEWLINE; + if (!data || !data->path) + return g_strdup(_("Unable to find log path!")); + if (g_file_get_contents(data->path, &read, NULL, NULL)) { + char *minus_header = strchr(read, '\n'); + + if (!minus_header) + return read; + + minus_header = g_strdup(minus_header + 1); + g_free(read); + + return minus_header; + } + return g_strdup_printf(_("Could not read file: %s"), data->path); +} + +static int html_logger_total_size(GaimLogType type, const char *name, GaimAccount *account) +{ + return gaim_log_common_total_sizer(type, name, account, ".html"); +} + + +/**************************** + ** PLAIN TEXT LOGGER ******* + ****************************/ + +static gsize txt_logger_write(GaimLog *log, + GaimMessageFlags type, + const char *from, time_t time, const char *message) +{ + char *date; + GaimPlugin *plugin = gaim_find_prpl(gaim_account_get_protocol_id(log->account)); + GaimLogCommonLoggerData *data = log->logger_data; + char *stripped = NULL; + + gsize written = 0; + + if (data == NULL) { + /* This log is new. We could use the loggers 'new' function, but + * creating a new file there would result in empty files in the case + * that you open a convo with someone, but don't say anything. + */ + const char *prpl = + GAIM_PLUGIN_PROTOCOL_INFO(plugin)->list_icon(log->account, NULL); + gaim_log_common_writer(log, ".txt"); + + data = log->logger_data; + + /* if we can't write to the file, give up before we hurt ourselves */ + if(!data->file) + return 0; + + if (log->type == GAIM_LOG_SYSTEM) + written += fprintf(data->file, "System log for account %s (%s) connected at %s\n", + gaim_account_get_username(log->account), prpl, + gaim_date_format_full(localtime(&log->time))); + else + written += fprintf(data->file, "Conversation with %s at %s on %s (%s)\n", + log->name, gaim_date_format_full(localtime(&log->time)), + gaim_account_get_username(log->account), prpl); + } + + /* if we can't write to the file, give up before we hurt ourselves */ + if(!data->file) + return 0; + + stripped = gaim_markup_strip_html(message); + date = log_get_timestamp(log, time); + + if(log->type == GAIM_LOG_SYSTEM){ + written += fprintf(data->file, "---- %s @ %s ----\n", stripped, date); + } else { + if (type & GAIM_MESSAGE_SEND || + type & GAIM_MESSAGE_RECV) { + if (type & GAIM_MESSAGE_AUTO_RESP) { + written += fprintf(data->file, _("(%s) %s : %s\n"), date, + from, stripped); + } else { + if(gaim_message_meify(stripped, -1)) + written += fprintf(data->file, "(%s) ***%s %s\n", date, from, + stripped); + else + written += fprintf(data->file, "(%s) %s: %s\n", date, from, + stripped); + } + } else if (type & GAIM_MESSAGE_SYSTEM) + written += fprintf(data->file, "(%s) %s\n", date, stripped); + else if (type & GAIM_MESSAGE_NO_LOG) { + /* This shouldn't happen */ + g_free(stripped); + return written; + } else if (type & GAIM_MESSAGE_WHISPER) + written += fprintf(data->file, "(%s) *%s* %s", date, from, stripped); + else + written += fprintf(data->file, "(%s) %s%s %s\n", date, from ? from : "", + from ? ":" : "", stripped); + } + g_free(date); + g_free(stripped); + fflush(data->file); + + return written; +} + +static void txt_logger_finalize(GaimLog *log) +{ + GaimLogCommonLoggerData *data = log->logger_data; + if (data) { + if(data->file) + fclose(data->file); + g_free(data->path); + + g_slice_free(GaimLogCommonLoggerData, data); + } +} + +static GList *txt_logger_list(GaimLogType type, const char *sn, GaimAccount *account) +{ + return gaim_log_common_lister(type, sn, account, ".txt", txt_logger); +} + +static GList *txt_logger_list_syslog(GaimAccount *account) +{ + return gaim_log_common_lister(GAIM_LOG_SYSTEM, ".system", account, ".txt", txt_logger); +} + +static char *txt_logger_read(GaimLog *log, GaimLogReadFlags *flags) +{ + char *read, *minus_header, *minus_header2; + GaimLogCommonLoggerData *data = log->logger_data; + *flags = 0; + if (!data || !data->path) + return g_strdup(_("Unable to find log path!")); + if (g_file_get_contents(data->path, &read, NULL, NULL)) { + minus_header = strchr(read, '\n'); + if (!minus_header) + minus_header = g_strdup(read); + else + minus_header = g_strdup(minus_header + 1); + g_free(read); + minus_header2 = g_markup_escape_text(minus_header, -1); + g_free(minus_header); + read = gaim_markup_linkify(minus_header2); + g_free(minus_header2); + return read; + } + return g_strdup_printf(_("Could not read file: %s"), data->path); +} + +static int txt_logger_total_size(GaimLogType type, const char *name, GaimAccount *account) +{ + return gaim_log_common_total_sizer(type, name, account, ".txt"); +} + + +/**************** + * OLD LOGGER *** + ****************/ + +/* The old logger doesn't write logs, only reads them. This is to include + * old logs in the log viewer transparently. + */ + +struct old_logger_data { + GaimStringref *pathref; + int offset; + int length; +}; + +static GList *old_logger_list(GaimLogType type, const char *sn, GaimAccount *account) +{ + char *logfile = g_strdup_printf("%s.log", gaim_normalize(account, sn)); + char *pathstr = g_build_filename(gaim_user_dir(), "logs", logfile, NULL); + GaimStringref *pathref = gaim_stringref_new(pathstr); + struct stat st; + time_t log_last_modified; + FILE *index; + FILE *file; + int index_fd; + char *index_tmp; + char buf[BUF_LONG]; + struct tm tm; + char month[4]; + struct old_logger_data *data = NULL; + char *newlog; + int logfound = 0; + int lastoff = 0; + int newlen; + time_t lasttime = 0; + + GaimLog *log = NULL; + GList *list = NULL; + + g_free(logfile); + + if (g_stat(gaim_stringref_value(pathref), &st)) + { + gaim_stringref_unref(pathref); + g_free(pathstr); + return NULL; + } + else + log_last_modified = st.st_mtime; + + /* Change the .log extension to .idx */ + strcpy(pathstr + strlen(pathstr) - 3, "idx"); + + if (g_stat(pathstr, &st) == 0) + { + if (st.st_mtime < log_last_modified) + { + gaim_debug_warning("log", "Index \"%s\" exists, but is older than the log.\n", pathstr); + } + else + { + /* The index file exists and is at least as new as the log, so open it. */ + if (!(index = g_fopen(pathstr, "rb"))) + { + gaim_debug_error("log", "Failed to open index file \"%s\" for reading: %s\n", + pathstr, strerror(errno)); + + /* Fall through so that we'll parse the log file. */ + } + else + { + gaim_debug_info("log", "Using index: %s\n", pathstr); + g_free(pathstr); + while (fgets(buf, BUF_LONG, index)) + { + unsigned long idx_time; + if (sscanf(buf, "%d\t%d\t%lu", &lastoff, &newlen, &idx_time) == 3) + { + log = gaim_log_new(GAIM_LOG_IM, sn, account, NULL, -1, NULL); + log->logger = old_logger; + log->time = (time_t)idx_time; + + /* IMPORTANT: Always set all members of struct old_logger_data */ + data = g_slice_new(struct old_logger_data); + + data->pathref = gaim_stringref_ref(pathref); + data->offset = lastoff; + data->length = newlen; + + log->logger_data = data; + list = g_list_prepend(list, log); + } + } + fclose(index); + + return list; + } + } + } + + if (!(file = g_fopen(gaim_stringref_value(pathref), "rb"))) { + gaim_debug_error("log", "Failed to open log file \"%s\" for reading: %s\n", + gaim_stringref_value(pathref), strerror(errno)); + gaim_stringref_unref(pathref); + g_free(pathstr); + return NULL; + } + + index_tmp = g_strdup_printf("%s.XXXXXX", pathstr); + if ((index_fd = g_mkstemp(index_tmp)) == -1) { + gaim_debug_error("log", "Failed to open index temp file: %s\n", + strerror(errno)); + g_free(pathstr); + g_free(index_tmp); + index = NULL; + } else { + if ((index = fdopen(index_fd, "wb")) == NULL) + { + gaim_debug_error("log", "Failed to fdopen() index temp file: %s\n", + strerror(errno)); + close(index_fd); + if (index_tmp != NULL) + { + g_unlink(index_tmp); + g_free(index_tmp); + } + g_free(pathstr); + } + } + + while (fgets(buf, BUF_LONG, file)) { + if ((newlog = strstr(buf, "---- New C"))) { + int length; + int offset; + char convostart[32]; + char *temp = strchr(buf, '@'); + + if (temp == NULL || strlen(temp) < 2) + continue; + + temp++; + length = strcspn(temp, "-"); + if (length > 31) length = 31; + + offset = ftell(file); + + if (logfound) { + newlen = offset - lastoff - length; + if(strstr(buf, "----
")) { + newlen -= + sizeof("

---- New Conversation @ ") + + sizeof("----


") - 2; + } else { + newlen -= + sizeof("---- New Conversation @ ") + sizeof("----") - 2; + } + + if(strchr(buf, '\r')) + newlen--; + + if (newlen != 0) { + log = gaim_log_new(GAIM_LOG_IM, sn, account, NULL, -1, NULL); + log->logger = old_logger; + log->time = lasttime; + + /* IMPORTANT: Always set all members of struct old_logger_data */ + data = g_slice_new(struct old_logger_data); + + data->pathref = gaim_stringref_ref(pathref); + data->offset = lastoff; + data->length = newlen; + + log->logger_data = data; + list = g_list_prepend(list, log); + + /* XXX: There is apparently Is there a proper way to print a time_t? */ + if (index != NULL) + fprintf(index, "%d\t%d\t%lu\n", data->offset, data->length, (unsigned long)log->time); + } + } + + logfound = 1; + lastoff = offset; + + g_snprintf(convostart, length, "%s", temp); + sscanf(convostart, "%*s %s %d %d:%d:%d %d", + month, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec, &tm.tm_year); + /* Ugly hack, in case current locale is not English */ + 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; + } + tm.tm_year -= 1900; + lasttime = mktime(&tm); + } + } + + if (logfound) { + if ((newlen = ftell(file) - lastoff) != 0) { + log = gaim_log_new(GAIM_LOG_IM, sn, account, NULL, -1, NULL); + log->logger = old_logger; + log->time = lasttime; + + /* IMPORTANT: Always set all members of struct old_logger_data */ + data = g_slice_new(struct old_logger_data); + + data->pathref = gaim_stringref_ref(pathref); + data->offset = lastoff; + data->length = newlen; + + log->logger_data = data; + list = g_list_prepend(list, log); + + /* XXX: Is there a proper way to print a time_t? */ + if (index != NULL) + fprintf(index, "%d\t%d\t%d\n", data->offset, data->length, (int)log->time); + } + } + + gaim_stringref_unref(pathref); + fclose(file); + if (index != NULL) + { + fclose(index); + + if (index_tmp == NULL) + { + g_free(pathstr); + g_return_val_if_reached(list); + } + + if (g_rename(index_tmp, pathstr)) + { + gaim_debug_warning("log", "Failed to rename index temp file \"%s\" to \"%s\": %s\n", + index_tmp, pathstr, strerror(errno)); + g_unlink(index_tmp); + g_free(index_tmp); + } + else + gaim_debug_info("log", "Built index: %s\n", pathstr); + + g_free(pathstr); + } + return list; +} + +static int old_logger_total_size(GaimLogType type, const char *name, GaimAccount *account) +{ + char *logfile = g_strdup_printf("%s.log", gaim_normalize(account, name)); + char *pathstr = g_build_filename(gaim_user_dir(), "logs", logfile, NULL); + int size; + struct stat st; + + if (g_stat(pathstr, &st)) + size = 0; + else + size = st.st_size; + + g_free(logfile); + g_free(pathstr); + + return size; +} + +static char * old_logger_read (GaimLog *log, GaimLogReadFlags *flags) +{ + struct old_logger_data *data = log->logger_data; + FILE *file = g_fopen(gaim_stringref_value(data->pathref), "rb"); + char *tmp, *read = g_malloc(data->length + 1); + fseek(file, data->offset, SEEK_SET); + fread(read, data->length, 1, file); + fclose(file); + read[data->length] = '\0'; + *flags = 0; + if(strstr(read, "
")) + *flags |= GAIM_LOG_READ_NO_NEWLINE; + else { + tmp = g_markup_escape_text(read, -1); + g_free(read); + read = gaim_markup_linkify(tmp); + g_free(tmp); + } + return read; +} + +static int old_logger_size (GaimLog *log) +{ + struct old_logger_data *data = log->logger_data; + return data ? data->length : 0; +} + +static void old_logger_get_log_sets(GaimLogSetCallback cb, GHashTable *sets) +{ + char *log_path = g_build_filename(gaim_user_dir(), "logs", NULL); + GDir *log_dir = g_dir_open(log_path, 0, NULL); + gchar *name; + GaimBlistNode *gnode, *cnode, *bnode; + + g_free(log_path); + if (log_dir == NULL) + return; + + /* Don't worry about the cast, name will be filled with a dynamically allocated data shortly. */ + while ((name = (gchar *)g_dir_read_name(log_dir)) != NULL) { + size_t len; + gchar *ext; + GaimLogSet *set; + gboolean found = FALSE; + + /* Unescape the filename. */ + name = g_strdup(gaim_unescape_filename(name)); + + /* Get the (possibly new) length of name. */ + len = strlen(name); + + if (len < 5) { + g_free(name); + continue; + } + + /* Make sure we're dealing with a log file. */ + ext = &name[len - 4]; + if (strcmp(ext, ".log")) { + g_free(name); + continue; + } + + /* IMPORTANT: Always set all members of GaimLogSet */ + set = g_slice_new(GaimLogSet); + + /* Chat for .chat at the end of the name to determine the type. */ + *ext = '\0'; + set->type = GAIM_LOG_IM; + if (len > 9) { + char *tmp = &name[len - 9]; + if (!strcmp(tmp, ".chat")) { + set->type = GAIM_LOG_CHAT; + *tmp = '\0'; + } + } + + set->name = set->normalized_name = name; + + /* Search the buddy list to find the account and to determine if this is a buddy. */ + for (gnode = gaim_get_blist()->root; !found && gnode != NULL; gnode = gnode->next) + { + if (!GAIM_BLIST_NODE_IS_GROUP(gnode)) + continue; + + for (cnode = gnode->child; !found && cnode != NULL; cnode = cnode->next) + { + if (!GAIM_BLIST_NODE_IS_CONTACT(cnode)) + continue; + + for (bnode = cnode->child; !found && bnode != NULL; bnode = bnode->next) + { + GaimBuddy *buddy = (GaimBuddy *)bnode; + + if (!strcmp(buddy->name, name)) { + set->account = buddy->account; + set->buddy = TRUE; + found = TRUE; + } + } + } + } + + if (!found) + { + set->account = NULL; + set->buddy = FALSE; + } + + cb(sets, set); + } + g_dir_close(log_dir); +} + +static void old_logger_finalize(GaimLog *log) +{ + struct old_logger_data *data = log->logger_data; + gaim_stringref_unref(data->pathref); + g_slice_free(struct old_logger_data, data); +} diff -r d10dda2777a9 -r b63ebf84c42b core/log.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/log.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,489 @@ +/** + * @file log.h Logging API + * @ingroup core + * + * 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 + */ +#ifndef _GAIM_LOG_H_ +#define _GAIM_LOG_H_ + +#include + + +/******************************************************** + * DATA STRUCTURES ************************************** + ********************************************************/ + +typedef struct _GaimLog GaimLog; +typedef struct _GaimLogLogger GaimLogLogger; +typedef struct _GaimLogCommonLoggerData GaimLogCommonLoggerData; +typedef struct _GaimLogSet GaimLogSet; + +typedef enum { + GAIM_LOG_IM, + GAIM_LOG_CHAT, + GAIM_LOG_SYSTEM +} GaimLogType; + +typedef enum { + GAIM_LOG_READ_NO_NEWLINE = 1 +} GaimLogReadFlags; + +#include "account.h" +#include "conversation.h" + +typedef void (*GaimLogSetCallback) (GHashTable *sets, GaimLogSet *set); + +/** + * A log logger. + * + * This struct gets filled out and is included in the GaimLog. It contains everything + * needed to write and read from logs. + */ +struct _GaimLogLogger { + char *name; /**< The logger's name */ + char *id; /**< an identifier to refer to this logger */ + + /** This gets called when the log is first created. + I don't think this is actually needed. */ + void (*create)(GaimLog *log); + + /** This is used to write to the log file */ + gsize (*write)(GaimLog *log, + GaimMessageFlags type, + const char *from, + time_t time, + const char *message); + + /** Called when the log is destroyed */ + void (*finalize)(GaimLog *log); + + /** This function returns a sorted GList of available GaimLogs */ + GList *(*list)(GaimLogType type, const char *name, GaimAccount *account); + + /** Given one of the logs returned by the logger's list function, + * this returns the contents of the log in GtkIMHtml markup */ + char *(*read)(GaimLog *log, GaimLogReadFlags *flags); + + /** Given one of the logs returned by the logger's list function, + * this returns the size of the log in bytes */ + int (*size)(GaimLog *log); + + /** Returns the total size of all the logs. If this is undefined a default + * implementation is used */ + int (*total_size)(GaimLogType type, const char *name, GaimAccount *account); + + /** This function returns a sorted GList of available system GaimLogs */ + GList *(*list_syslog)(GaimAccount *account); + + /** Adds GaimLogSets to a GHashTable. By passing the data in the GaimLogSets + * to list, the caller can get every available GaimLog from the logger. + * Loggers using gaim_log_common_writer() (or otherwise storing their + * logs in the same directory structure as the stock loggers) do not + * need to implement this function. + * + * Loggers which implement this function must create a GaimLogSet, + * then call @a cb with @a sets and the newly created GaimLogSet. */ + void (*get_log_sets)(GaimLogSetCallback cb, GHashTable *sets); +}; + +/** + * A log. Not the wooden type. + */ +struct _GaimLog { + GaimLogType type; /**< The type of log this is */ + char *name; /**< The name of this log */ + GaimAccount *account; /**< The account this log is taking + place on */ + GaimConversation *conv; /**< The conversation being logged */ + time_t time; /**< The time this conversation + started, converted to the local timezone */ + + GaimLogLogger *logger; /**< The logging mechanism this log + is to use */ + void *logger_data; /**< Data used by the log logger */ + struct tm *tm; /**< The time this conversation + started, saved with original + timezone data, if available and + if struct tm has the BSD + timezone fields, else @c NULL. + Do NOT modify anything in this struct.*/ + + /* IMPORTANT: Some code in log.c allocates these without zeroing them. + * IMPORTANT: Update that code if you add members here. */ +}; + +/** + * A common logger_data struct containing a file handle and path, as well + * as a pointer to something else for additional data. + */ +struct _GaimLogCommonLoggerData { + char *path; + FILE *file; + void *extra_data; +}; + +/** + * Describes available logs. + * + * By passing the elements of this struct to gaim_log_get_logs(), the caller + * can get all available GaimLogs. + */ +struct _GaimLogSet { + GaimLogType type; /**< The type of logs available */ + char *name; /**< The name of the logs available */ + GaimAccount *account; /**< The account the available logs + took place on. This will be + @c NULL if the account no longer + exists. (Depending on a + logger's implementation of + list, it may not be possible + to load such logs.) */ + gboolean buddy; /**< Is this (account, name) a buddy + on the buddy list? */ + char *normalized_name; /**< The normalized version of + @a name. It must be set, and + may be set to the same pointer + value as @a name. */ + + /* IMPORTANT: Some code in log.c allocates these without zeroing them. + * IMPORTANT: Update that code if you add members here. */ +}; + +#ifdef __cplusplus +extern "C" { +#endif + +/***************************************/ +/** @name Log Functions */ +/***************************************/ +/*@{*/ + +/** + * Creates a new log + * + * @param type The type of log this is. + * @param name The name of this conversation (screenname, chat name, + * etc.) + * @param account The account the conversation is occurring on + * @param conv The conversation being logged + * @param time The time this conversation started + * @param tm The time this conversation started, with timezone data, + * if available and if struct tm has the BSD timezone fields. + * @return The new log + */ +GaimLog *gaim_log_new(GaimLogType type, const char *name, GaimAccount *account, + GaimConversation *conv, time_t time, const struct tm *tm); + +/** + * Frees a log + * + * @param log The log to destroy + */ +void gaim_log_free(GaimLog *log); + +/** + * Writes to a log file. Assumes you have checked preferences already. + * + * @param log The log to write to + * @param type The type of message being logged + * @param from Whom this message is coming from, or @c NULL for + * system messages + * @param time A timestamp in UNIX time + * @param message The message to log + */ +void gaim_log_write(GaimLog *log, + GaimMessageFlags type, + const char *from, + time_t time, + const char *message); + +/** + * Reads from a log + * + * @param log The log to read from + * @param flags The returned logging flags. + * + * @return The contents of this log in Gaim Markup. + */ +char *gaim_log_read(GaimLog *log, GaimLogReadFlags *flags); + +/** + * Returns a list of all available logs + * + * @param type The type of the log + * @param name The name of the log + * @param account The account + * @return A sorted list of GaimLogs + */ +GList *gaim_log_get_logs(GaimLogType type, const char *name, GaimAccount *account); + +/** + * Returns a GHashTable of GaimLogSets. + * + * A "log set" here means the information necessary to gather the + * GaimLogs for a given buddy/chat. This information would be passed + * to gaim_log_list to get a list of GaimLogs. + * + * The primary use of this function is to get a list of everyone the + * user has ever talked to (assuming he or she uses logging). + * + * The GHashTable that's returned will free all log sets in it when + * destroyed. If a GaimLogSet is removed from the GHashTable, it + * must be freed with gaim_log_set_free(). + * + * @return A GHashTable of all available unique GaimLogSets + */ +GHashTable *gaim_log_get_log_sets(void); + +/** + * Returns a list of all available system logs + * + * @param account The account + * @return A sorted list of GaimLogs + */ +GList *gaim_log_get_system_logs(GaimAccount *account); + +/** + * Returns the size of a log + * + * @param log The log + * @return The size of the log, in bytes + */ +int gaim_log_get_size(GaimLog *log); + +/** + * Returns the size, in bytes, of all available logs in this conversation + * + * @param type The type of the log + * @param name The name of the log + * @param account The account + * @return The size in bytes + */ +int gaim_log_get_total_size(GaimLogType type, const char *name, GaimAccount *account); + +/** + * Returns the default logger directory Gaim uses for a given account + * and username. This would be where Gaim stores logs created by + * the built-in text or HTML loggers. + * + * @param type The type of the log. + * @param name The name of the log. + * @param account The account. + * @return The default logger directory for Gaim. + */ +char *gaim_log_get_log_dir(GaimLogType type, const char *name, GaimAccount *account); + +/** + * Implements GCompareFunc for GaimLogs + * + * @param y A GaimLog + * @param z Another GaimLog + * @return A value as specified by GCompareFunc + */ +gint gaim_log_compare(gconstpointer y, gconstpointer z); + +/** + * Implements GCompareFunc for GaimLogSets + * + * @param y A GaimLogSet + * @param z Another GaimLogSet + * @return A value as specified by GCompareFunc + */ +gint gaim_log_set_compare(gconstpointer y, gconstpointer z); + +/** + * Frees a log set + * + * @param set The log set to destroy + */ +void gaim_log_set_free(GaimLogSet *set); + +/*@}*/ + +/******************************************/ +/** @name Common Logger Functions */ +/******************************************/ +/*@{*/ + +/** + * Opens a new log file in the standard Gaim log location + * with the given file extension, named for the current time, + * for writing. If a log file is already open, the existing + * file handle is retained. The log's logger_data value is + * set to a GaimLogCommonLoggerData struct containing the log + * file handle and log path. + * + * @param log The log to write to. + * @param ext The file extension to give to this log file. + */ +void gaim_log_common_writer(GaimLog *log, const char *ext); + +/** + * Returns a sorted GList of GaimLogs of the requested type. + * This function should only be used with logs that are written + * with gaim_log_common_writer(). + * + * @param type The type of the logs being listed. + * @param name The name of the log. + * @param account The account of the log. + * @param ext The file extension this log format uses. + * @param logger A reference to the logger struct for this log. + * + * @return A sorted GList of GaimLogs matching the parameters. + */ +GList *gaim_log_common_lister(GaimLogType type, const char *name, + GaimAccount *account, const char *ext, + GaimLogLogger *logger); + +/** + * Returns the total size of all the logs for a given user, with + * a given extension. This is the "common" implemention of a + * logger's total_size function. + * This function should only be used with logs that are written + * with gaim_log_common_writer(). + * + * @param type The type of the logs being sized. + * @param name The name of the logs to size + * (e.g. the username or chat name). + * @param account The account of the log. + * @param ext The file extension this log format uses. + * + * @return The size of all the logs with the specified extension + * for the specified user. + */ +int gaim_log_common_total_sizer(GaimLogType type, const char *name, + GaimAccount *account, const char *ext); + +/** + * Returns the size of a given GaimLog. + * This function should only be used with logs that are written + * with gaim_log_common_writer(). + * + * @param log The GaimLog to size. + * + * @return An integer indicating the size of the log in bytes. + */ +int gaim_log_common_sizer(GaimLog *log); +/*@}*/ + +/******************************************/ +/** @name Logger Functions */ +/******************************************/ +/*@{*/ + +/** + * Creates a new logger + * + * @param id The logger's id. + * @param name The logger's name. + * @param functions The number of functions being passed. The following + * functions are currently available (in order): @c create, + * @c write, @c finalize, @c list, @c read, @c size, + * @c total_size, @c list_syslog, @c get_log_sets. For + * details on these functions, see GaimLogLogger. + * Functions may not be skipped. For example, passing + * @c create and @c write is acceptable (for a total of + * two functions). Passing @c create and @c finalize, + * however, is not. To accomplish that, the caller must + * pass @c create, @c NULL (a placeholder for @c write), + * and @c finalize (for a total of 3 functions). + * + * @return The new logger + */ +GaimLogLogger *gaim_log_logger_new(const char *id, const char *name, int functions, ...); + +/** + * Frees a logger + * + * @param logger The logger to free + */ +void gaim_log_logger_free(GaimLogLogger *logger); + +/** + * Adds a new logger + * + * @param logger The new logger to add + */ +void gaim_log_logger_add (GaimLogLogger *logger); + +/** + * + * Removes a logger + * + * @param logger The logger to remove + */ +void gaim_log_logger_remove (GaimLogLogger *logger); + +/** + * + * Sets the current logger + * + * @param logger The logger to set + */ +void gaim_log_logger_set (GaimLogLogger *logger); + +/** + * + * Returns the current logger + * + * @return logger The current logger + */ +GaimLogLogger *gaim_log_logger_get (void); + +/** + * Returns a GList containing the IDs and names of the registered + * loggers. + * + * @return The list of IDs and names. + */ +GList *gaim_log_logger_get_options(void); + +/**************************************************************************/ +/** @name Log Subsystem */ +/**************************************************************************/ +/*@{*/ + +/** + * Initializes the log subsystem. + */ +void gaim_log_init(void); + +/** + * Returns the log subsystem handle. + * + * @return The log subsystem handle. + */ +void *gaim_log_get_handle(void); + +/** + * Uninitializes the log subsystem. + */ +void gaim_log_uninit(void); + +/*@}*/ + + +#ifdef __cplusplus +} +#endif + +#endif /* _GAIM_LOG_H_ */ diff -r d10dda2777a9 -r b63ebf84c42b core/mime.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/mime.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,562 @@ +/* + * 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 +#include + +#include +#include +#include +#include + +/* this should become "util.h" if we ever get this into gaim proper */ +#include "debug.h" +#include "mime.h" +#include "util.h" + +/** + * @struct mime_fields + * + * Utility structure used in both MIME document and parts, which maps + * field names to their values, and keeps an easily accessible list of + * keys. + */ +struct mime_fields { + GHashTable *map; + GList *keys; +}; + +struct _GaimMimeDocument { + struct mime_fields fields; + GList *parts; +}; + +struct _GaimMimePart { + struct mime_fields fields; + struct _GaimMimeDocument *doc; + GString *data; +}; + +static void +fields_set(struct mime_fields *mf, const char *key, const char *val) +{ + char *k, *v; + + g_return_if_fail(mf != NULL); + g_return_if_fail(mf->map != NULL); + + k = g_ascii_strdown(key, -1); + v = g_strdup(val); + + /* append to the keys list only if it's not already there */ + if(! g_hash_table_lookup(mf->map, k)) { + mf->keys = g_list_append(mf->keys, k); + } + + /* important to use insert. If the key is already in the table, then + it's already in the keys list. Insert will free the new instance + of the key rather than the old instance. */ + g_hash_table_insert(mf->map, k, v); +} + + +static const char * +fields_get(struct mime_fields *mf, const char *key) +{ + char *kdown; + const char *ret; + + g_return_val_if_fail(mf != NULL, NULL); + g_return_val_if_fail(mf->map != NULL, NULL); + + kdown = g_ascii_strdown(key, -1); + ret = g_hash_table_lookup(mf->map, kdown); + g_free(kdown); + + return ret; +} + + +static void +fields_init(struct mime_fields *mf) +{ + g_return_if_fail(mf != NULL); + + mf->map = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, g_free); +} + + +static void +fields_loadline(struct mime_fields *mf, const char *line, gsize len) +{ + /* split the line into key: value */ + char *key, *val; + char **tokens; + + /* feh, need it to be NUL terminated */ + key = g_strndup(line, len); + + /* split */ + val = strchr(key, ':'); + if(! val) { + g_free(key); + return; + } + *val++ = '\0'; + + /* normalize whitespace (sorta) and trim on key and value */ + tokens = g_strsplit(key, "\t\r\n", 0); + key = g_strjoinv("", tokens); + key = g_strstrip(key); + g_strfreev(tokens); + + tokens = g_strsplit(val, "\t\r\n", 0); + val = g_strjoinv("", tokens); + val = g_strstrip(val); + g_strfreev(tokens); + + fields_set(mf, key, val); + + g_free(key); + g_free(val); +} + + +static void +fields_load(struct mime_fields *mf, char **buf, gsize *len) +{ + char *tail; + + while ((tail = g_strstr_len(*buf, *len, "\r\n"))) + { + char *line; + gsize ln; + + /* determine the current line */ + line = *buf; + ln = tail - line; + + /* advance our search space past the CRLF */ + *buf = tail + 2; + *len -= (ln + 2); + + /* empty line, end of headers */ + if(! ln) return; + + /* look out for line continuations */ + if(line[ln-1] == ';') { + tail = g_strstr_len(*buf, *len, "\r\n"); + if(tail) { + gsize cln; + + cln = tail - *buf; + ln = tail - line; + + /* advance our search space past the CRLF (again) */ + *buf = tail + 2; + *len -= (cln + 2); + } + } + + /* process our super-cool line */ + fields_loadline(mf, line, ln); + } +} + + +static void +field_write(const char *key, const char *val, GString *str) +{ + g_string_append_printf(str, "%s: %s\r\n", key, val); +} + + +static void +fields_write(struct mime_fields *mf, GString *str) +{ + g_return_if_fail(mf != NULL); + + g_hash_table_foreach(mf->map, (GHFunc) field_write, str); + g_string_append(str, "\r\n"); +} + + +static void +fields_destroy(struct mime_fields *mf) +{ + g_return_if_fail(mf != NULL); + + g_hash_table_destroy(mf->map); + g_list_free(mf->keys); + + mf->map = NULL; + mf->keys = NULL; +} + + +static GaimMimePart * +part_new(GaimMimeDocument *doc) +{ + GaimMimePart *part; + + part = g_new0(GaimMimePart, 1); + fields_init(&part->fields); + part->doc = doc; + part->data = g_string_new(NULL); + + doc->parts = g_list_prepend(doc->parts, part); + + return part; +} + + +static void +part_load(GaimMimePart *part, const char *buf, gsize len) +{ + + char *b = (char *) buf; + gsize n = len; + + fields_load(&part->fields, &b, &n); + + /* the remainder will have a blank line, if there's anything at all, + so check if there's anything then trim off the trailing four + bytes, \r\n\r\n */ + if(n > 4) n -= 4; + g_string_append_len(part->data, b, n); +} + + +static void +part_write(GaimMimePart *part, GString *str) +{ + fields_write(&part->fields, str); + g_string_append_printf(str, "%s\r\n\r\n", part->data->str); +} + + +static void +part_free(GaimMimePart *part) +{ + + fields_destroy(&part->fields); + + g_string_free(part->data, TRUE); + part->data = NULL; + + g_free(part); +} + + +GaimMimePart * +gaim_mime_part_new(GaimMimeDocument *doc) +{ + g_return_val_if_fail(doc != NULL, NULL); + return part_new(doc); +} + + +const GList * +gaim_mime_part_get_fields(GaimMimePart *part) +{ + g_return_val_if_fail(part != NULL, NULL); + return part->fields.keys; +} + + +const char * +gaim_mime_part_get_field(GaimMimePart *part, const char *field) +{ + g_return_val_if_fail(part != NULL, NULL); + return fields_get(&part->fields, field); +} + + +char * +gaim_mime_part_get_field_decoded(GaimMimePart *part, const char *field) +{ + const char *f; + + g_return_val_if_fail(part != NULL, NULL); + + f = fields_get(&part->fields, field); + return gaim_mime_decode_field(f); +} + + +void +gaim_mime_part_set_field(GaimMimePart *part, const char *field, const char *value) +{ + g_return_if_fail(part != NULL); + fields_set(&part->fields, field, value); +} + + +const char * +gaim_mime_part_get_data(GaimMimePart *part) +{ + g_return_val_if_fail(part != NULL, NULL); + g_return_val_if_fail(part->data != NULL, NULL); + + return part->data->str; +} + + +void +gaim_mime_part_get_data_decoded(GaimMimePart *part, guchar **data, gsize *len) +{ + const char *enc; + + g_return_if_fail(part != NULL); + g_return_if_fail(data != NULL); + g_return_if_fail(len != NULL); + + g_return_if_fail(part->data != NULL); + + enc = gaim_mime_part_get_field(part, "content-transfer-encoding"); + + if(! enc) { + *data = (guchar *)g_strdup(part->data->str); + *len = part->data->len; + + } else if(! g_ascii_strcasecmp(enc, "7bit")) { + *data = (guchar *)g_strdup(part->data->str); + *len = part->data->len; + + } else if(! g_ascii_strcasecmp(enc, "8bit")) { + *data = (guchar *)g_strdup(part->data->str); + *len = part->data->len; + + } else if(! g_ascii_strcasecmp(enc, "base16")) { + *data = gaim_base16_decode(part->data->str, len); + + } else if(! g_ascii_strcasecmp(enc, "base64")) { + *data = gaim_base64_decode(part->data->str, len); + + } else if(! g_ascii_strcasecmp(enc, "quoted-printable")) { + *data = gaim_quotedp_decode(part->data->str, len); + + } else { + gaim_debug_warning("mime", "gaim_mime_part_get_data_decoded:" + " unknown encoding '%s'\n", enc); + *data = NULL; + *len = 0; + } +} + + +gsize +gaim_mime_part_get_length(GaimMimePart *part) +{ + g_return_val_if_fail(part != NULL, 0); + g_return_val_if_fail(part->data != NULL, 0); + + return part->data->len; +} + + +void +gaim_mime_part_set_data(GaimMimePart *part, const char *data) +{ + g_return_if_fail(part != NULL); + g_string_free(part->data, TRUE); + part->data = g_string_new(data); +} + + +GaimMimeDocument * +gaim_mime_document_new() +{ + GaimMimeDocument *doc; + + doc = g_new0(GaimMimeDocument, 1); + fields_init(&doc->fields); + + return doc; +} + + +static void +doc_parts_load(GaimMimeDocument *doc, const char *boundary, const char *buf, gsize len) +{ + char *b = (char *) buf; + gsize n = len; + + const char *bnd; + gsize bl; + + bnd = g_strdup_printf("--%s", boundary); + bl = strlen(bnd); + + for(b = g_strstr_len(b, n, bnd); b; ) { + char *tail; + + /* skip the boundary */ + b += bl; + n -= bl; + + /* skip the trailing \r\n or -- as well */ + if(n >= 2) { + b += 2; + n -= 2; + } + + /* find the next boundary */ + tail = g_strstr_len(b, n, bnd); + + if(tail) { + gsize sl; + + sl = tail - b; + if(sl) { + GaimMimePart *part = part_new(doc); + part_load(part, b, sl); + } + } + + b = tail; + } +} + + +GaimMimeDocument * +gaim_mime_document_parsen(const char *buf, gsize len) +{ + GaimMimeDocument *doc; + + char *b = (char *) buf; + gsize n = len; + + g_return_val_if_fail(buf != NULL, NULL); + + doc = gaim_mime_document_new(); + + if (!len) + return doc; + + fields_load(&doc->fields, &b, &n); + + { + const char *ct = fields_get(&doc->fields, "content-type"); + if(ct && gaim_str_has_prefix(ct, "multipart")) { + char *bd = strrchr(ct, '='); + if(bd++) { + doc_parts_load(doc, bd, b, n); + } + } + } + + return doc; +} + + +GaimMimeDocument * +gaim_mime_document_parse(const char *buf) +{ + g_return_val_if_fail(buf != NULL, NULL); + return gaim_mime_document_parsen(buf, strlen(buf)); +} + + +void +gaim_mime_document_write(GaimMimeDocument *doc, GString *str) +{ + const char *bd = NULL; + + g_return_if_fail(doc != NULL); + g_return_if_fail(str != NULL); + + { + const char *ct = fields_get(&doc->fields, "content-type"); + if(ct && gaim_str_has_prefix(ct, "multipart")) { + char *b = strrchr(ct, '='); + if(b++) bd = b; + } + } + + fields_write(&doc->fields, str); + + if(bd) { + GList *l; + + for(l = doc->parts; l; l = l->next) { + g_string_append_printf(str, "--%s\r\n", bd); + + part_write(l->data, str); + + if(! l->next) { + g_string_append_printf(str, "--%s--\r\n", bd); + } + } + } +} + + +const GList * +gaim_mime_document_get_fields(GaimMimeDocument *doc) +{ + g_return_val_if_fail(doc != NULL, NULL); + return doc->fields.keys; +} + + +const char * +gaim_mime_document_get_field(GaimMimeDocument *doc, const char *field) +{ + g_return_val_if_fail(doc != NULL, NULL); + return fields_get(&doc->fields, field); +} + + +void +gaim_mime_document_set_field(GaimMimeDocument *doc, const char *field, const char *value) +{ + g_return_if_fail(doc != NULL); + fields_set(&doc->fields, field, value); +} + + +const GList * +gaim_mime_document_get_parts(GaimMimeDocument *doc) +{ + g_return_val_if_fail(doc != NULL, NULL); + return doc->parts; +} + + +void +gaim_mime_document_free(GaimMimeDocument *doc) +{ + if (!doc) + return; + + fields_destroy(&doc->fields); + + while(doc->parts) { + part_free(doc->parts->data); + doc->parts = g_list_delete_link(doc->parts, doc->parts); + } + + g_free(doc); +} diff -r d10dda2777a9 -r b63ebf84c42b core/mime.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/mime.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,211 @@ +/* + * 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. + */ + +#ifndef _GAIM_MIME_H +#define _GAIM_MIME_H + +#include +#include + +/** + * @file mime.h + * @ingroup core + * + * Rudimentary parsing of multi-part MIME messages into more + * accessible structures. + */ + +/** + * @typedef GaimMimeDocument A MIME document. + */ +typedef struct _GaimMimeDocument GaimMimeDocument; + +/** + * @typedef GaimMimePart A part of a multipart MIME document. + */ +typedef struct _GaimMimePart GaimMimePart; + +/** + * Allocate an empty MIME document. + */ +GaimMimeDocument *gaim_mime_document_new(void); + +/** + * Frees memory used in a MIME document and all of its parts and fields + * + * @param doc The MIME document to free. + */ +void gaim_mime_document_free(GaimMimeDocument *doc); + +/** + * Parse a MIME document from a NUL-terminated string. + * + * @param buf The NULL-terminated string containing the MIME-encoded data. + * + * @returns A MIME document. + */ +GaimMimeDocument *gaim_mime_document_parse(const char *buf); + +/** + * Parse a MIME document from a string + * + * @param buf The string containing the MIME-encoded data. + * @param len Length of buf. + * + * @returns A MIME document. + */ +GaimMimeDocument *gaim_mime_document_parsen(const char *buf, gsize len); + +/** + * Write (append) a MIME document onto a GString. + */ +void gaim_mime_document_write(GaimMimeDocument *doc, GString *str); + +/** + * The list of fields in the header of a document + * + * @param doc The MIME document. + * + * @returns A list of strings indicating the fields (but not the values of + * the fields) in the header of doc. + */ +const GList *gaim_mime_document_get_fields(GaimMimeDocument *doc); + +/** + * Get the value of a specific field in the header of a document. + * + * @param doc The MIME document. + * @param field Case-insensitive field name. + * + * @returns Value associated with the indicated header field, or + * NULL if the field doesn't exist. + */ +const char *gaim_mime_document_get_field(GaimMimeDocument *doc, + const char *field); + +/** + * Set or replace the value of a specific field in the header of a + * document. + * + * @param doc The MIME document. + * @param field Case-insensitive field name. + * @param value Value to associate with the indicated header field, + * of NULL to remove the field. + */ +void gaim_mime_document_set_field(GaimMimeDocument *doc, + const char *field, + const char *value); + +/** + * The list of parts in a multipart document. + * + * @param doc The MIME document. + * + * @returns List of GaimMimePart contained within doc. + */ +const GList *gaim_mime_document_get_parts(GaimMimeDocument *doc); + +/** + * Create and insert a new part into a MIME document. + * + * @param doc The new part's parent MIME document. + */ +GaimMimePart *gaim_mime_part_new(GaimMimeDocument *doc); + + +/** + * The list of fields in the header of a document part. + * + * @param part The MIME document part. + * + * @returns List of strings indicating the fields (but not the values + * of the fields) in the header of part. + */ +const GList *gaim_mime_part_get_fields(GaimMimePart *part); + + +/** + * Get the value of a specific field in the header of a document part. + * + * @param part The MIME document part. + * @param field Case-insensitive name of the header field. + * + * @returns Value of the specified header field, or NULL if the + * field doesn't exist. + */ +const char *gaim_mime_part_get_field(GaimMimePart *part, + const char *field); + +/** + * Get the decoded value of a specific field in the header of a + * document part. + */ +char *gaim_mime_part_get_field_decoded(GaimMimePart *part, + const char *field); + +/** + * Set or replace the value of a specific field in the header of a + * document. + * + * @param part The part of the MIME document. + * @param field Case-insensitive field name + * @param value Value to associate with the indicated header field, + * of NULL to remove the field. + */ +void gaim_mime_part_set_field(GaimMimePart *part, + const char *field, + const char *value); + +/** + * Get the (possibly encoded) data portion of a MIME document part. + * + * @param part The MIME document part. + * + * @returns NULL-terminated data found in the document part + */ +const char *gaim_mime_part_get_data(GaimMimePart *part); + +/** + * Get the data portion of a MIME document part, after attempting to + * decode it according to the content-transfer-encoding field. If the + * specified encoding method is not supported, this function will + * return NULL. + * + * @param part The MIME documemt part. + * @param data Buffer for the data. + * @param len The length of the buffer. + */ +void gaim_mime_part_get_data_decoded(GaimMimePart *part, + guchar **data, gsize *len); + +/** + * Get the length of the data portion of a MIME document part. + * + * @param part The MIME document part. + * @returns Length of the data in the document part. + */ +gsize gaim_mime_part_get_length(GaimMimePart *part); + +void gaim_mime_part_set_data(GaimMimePart *part, const char *data); + +#endif diff -r d10dda2777a9 -r b63ebf84c42b core/network.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/network.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,362 @@ +/** + * @file network.c Network Implementation + * @ingroup core + * + * 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" + +#ifndef _WIN32 +#include +#include +#endif + +/* Solaris */ +#if defined (__SVR4) && defined (__sun) +#include +#endif + +#include "debug.h" +#include "account.h" +#include "network.h" +#include "prefs.h" +#include "stun.h" +#include "upnp.h" + +typedef struct { + int listenfd; + int socket_type; + gboolean retry; + gboolean adding; + GaimNetworkListenCallback cb; + gpointer cb_data; +} ListenUPnPData; + +const unsigned char * +gaim_network_ip_atoi(const char *ip) +{ + static unsigned char ret[4]; + gchar *delimiter = "."; + gchar **split; + int i; + + g_return_val_if_fail(ip != NULL, NULL); + + split = g_strsplit(ip, delimiter, 4); + for (i = 0; split[i] != NULL; i++) + ret[i] = atoi(split[i]); + g_strfreev(split); + + /* i should always be 4 */ + if (i != 4) + return NULL; + + return ret; +} + +void +gaim_network_set_public_ip(const char *ip) +{ + g_return_if_fail(ip != NULL); + + /* XXX - Ensure the IP address is valid */ + + gaim_prefs_set_string("/core/network/public_ip", ip); +} + +const char * +gaim_network_get_public_ip(void) +{ + return gaim_prefs_get_string("/core/network/public_ip"); +} + +const char * +gaim_network_get_local_system_ip(int fd) +{ + char buffer[1024]; + static char ip[16]; + char *tmp; + struct ifconf ifc; + struct ifreq *ifr; + struct sockaddr_in *sinptr; + guint32 lhost = htonl(127 * 256 * 256 * 256 + 1); + long unsigned int add; + int source = fd; + + if (fd < 0) + source = socket(PF_INET,SOCK_STREAM, 0); + + ifc.ifc_len = sizeof(buffer); + ifc.ifc_req = (struct ifreq *)buffer; + ioctl(source, SIOCGIFCONF, &ifc); + + if (fd < 0) + close(source); + + tmp = buffer; + while (tmp < buffer + ifc.ifc_len) + { + ifr = (struct ifreq *)tmp; + tmp += sizeof(struct ifreq); + + if (ifr->ifr_addr.sa_family == AF_INET) + { + sinptr = (struct sockaddr_in *)&ifr->ifr_addr; + if (sinptr->sin_addr.s_addr != lhost) + { + add = ntohl(sinptr->sin_addr.s_addr); + g_snprintf(ip, 16, "%lu.%lu.%lu.%lu", + ((add >> 24) & 255), + ((add >> 16) & 255), + ((add >> 8) & 255), + add & 255); + + return ip; + } + } + } + + return "0.0.0.0"; +} + +const char * +gaim_network_get_my_ip(int fd) +{ + const char *ip = NULL; + GaimStunNatDiscovery *stun; + + /* Check if the user specified an IP manually */ + if (!gaim_prefs_get_bool("/core/network/auto_ip")) { + ip = gaim_network_get_public_ip(); + if ((ip != NULL) && (*ip != '\0')) + return ip; + } + + /* Check if STUN discovery was already done */ + stun = gaim_stun_discover(NULL); + if ((stun != NULL) && (stun->status == GAIM_STUN_STATUS_DISCOVERED)) + return stun->publicip; + + /* Attempt to get the IP from a NAT device using UPnP */ + ip = gaim_upnp_get_public_ip(); + if (ip != NULL) + return ip; + + /* Just fetch the IP of the local system */ + return gaim_network_get_local_system_ip(fd); +} + + +static void +gaim_network_set_upnp_port_mapping_cb(gboolean success, gpointer data) +{ + ListenUPnPData *ldata = data; + + if (!success) { + gaim_debug_info("network", "Couldn't create UPnP mapping\n"); + if (ldata->retry) { + ldata->retry = FALSE; + ldata->adding = FALSE; + gaim_upnp_remove_port_mapping( + gaim_network_get_port_from_fd(ldata->listenfd), + (ldata->socket_type == SOCK_STREAM) ? "TCP" : "UDP", + gaim_network_set_upnp_port_mapping_cb, ldata); + return; + } + } else if (!ldata->adding) { + /* We've tried successfully to remove the port mapping. + * Try to add it again */ + ldata->adding = TRUE; + gaim_upnp_set_port_mapping( + gaim_network_get_port_from_fd(ldata->listenfd), + (ldata->socket_type == SOCK_STREAM) ? "TCP" : "UDP", + gaim_network_set_upnp_port_mapping_cb, ldata); + return; + } + + if (ldata->cb) + ldata->cb(ldata->listenfd, ldata->cb_data); + + g_free(ldata); +} + + +static gboolean +gaim_network_do_listen(unsigned short port, int socket_type, GaimNetworkListenCallback cb, gpointer cb_data) +{ + int listenfd = -1; + const int on = 1; + ListenUPnPData *ld; +#ifdef HAVE_GETADDRINFO + int errnum; + struct addrinfo hints, *res, *next; + char serv[6]; + + /* + * Get a list of addresses on this machine. + */ + snprintf(serv, sizeof(serv), "%hu", port); + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_flags = AI_PASSIVE; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = socket_type; + errnum = getaddrinfo(NULL /* any IP */, serv, &hints, &res); + if (errnum != 0) { +#ifndef _WIN32 + gaim_debug_warning("network", "getaddrinfo: %s\n", gai_strerror(errnum)); + if (errnum == EAI_SYSTEM) + gaim_debug_warning("network", "getaddrinfo: system error: %s\n", strerror(errno)); +#else + gaim_debug_warning("network", "getaddrinfo: Error Code = %d\n", errnum); +#endif + return FALSE; + } + + /* + * Go through the list of addresses and attempt to listen on + * one of them. + * XXX - Try IPv6 addresses first? + */ + for (next = res; next != NULL; next = next->ai_next) { + listenfd = socket(next->ai_family, next->ai_socktype, next->ai_protocol); + if (listenfd < 0) + continue; + if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) != 0) + gaim_debug_warning("network", "setsockopt: %s\n", strerror(errno)); + if (bind(listenfd, next->ai_addr, next->ai_addrlen) == 0) + break; /* success */ + /* XXX - It is unclear to me (datallah) whether we need to be + using a new socket each time */ + close(listenfd); + } + + freeaddrinfo(res); + + if (next == NULL) + return FALSE; +#else + struct sockaddr_in sockin; + + if ((listenfd = socket(AF_INET, socket_type, 0)) < 0) { + gaim_debug_warning("network", "socket: %s\n", strerror(errno)); + return FALSE; + } + + if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) != 0) + gaim_debug_warning("network", "setsockopt: %s\n", strerror(errno)); + + memset(&sockin, 0, sizeof(struct sockaddr_in)); + sockin.sin_family = PF_INET; + sockin.sin_port = htons(port); + + if (bind(listenfd, (struct sockaddr *)&sockin, sizeof(struct sockaddr_in)) != 0) { + gaim_debug_warning("network", "bind: %s\n", strerror(errno)); + close(listenfd); + return FALSE; + } +#endif + + if (socket_type == SOCK_STREAM && listen(listenfd, 4) != 0) { + gaim_debug_warning("network", "listen: %s\n", strerror(errno)); + close(listenfd); + return FALSE; + } + fcntl(listenfd, F_SETFL, O_NONBLOCK); + + gaim_debug_info("network", "Listening on port: %hu\n", gaim_network_get_port_from_fd(listenfd)); + + ld = g_new0(ListenUPnPData, 1); + ld->listenfd = listenfd; + ld->adding = TRUE; + ld->retry = TRUE; + ld->cb = cb; + ld->cb_data = cb_data; + + gaim_upnp_set_port_mapping( + gaim_network_get_port_from_fd(listenfd), + (socket_type == SOCK_STREAM) ? "TCP" : "UDP", + gaim_network_set_upnp_port_mapping_cb, ld); + + return TRUE; +} + +gboolean +gaim_network_listen(unsigned short port, int socket_type, + GaimNetworkListenCallback cb, gpointer cb_data) +{ + g_return_val_if_fail(port != 0, -1); + + return gaim_network_do_listen(port, socket_type, cb, cb_data); +} + +gboolean +gaim_network_listen_range(unsigned short start, unsigned short end, + int socket_type, GaimNetworkListenCallback cb, gpointer cb_data) +{ + gboolean ret = FALSE; + + if (gaim_prefs_get_bool("/core/network/ports_range_use")) { + start = gaim_prefs_get_int("/core/network/ports_range_start"); + end = gaim_prefs_get_int("/core/network/ports_range_end"); + } else { + if (end < start) + end = start; + } + + for (; start <= end; start++) { + ret = gaim_network_do_listen(start, socket_type, cb, cb_data); + if (ret) + break; + } + + return ret; +} + +unsigned short +gaim_network_get_port_from_fd(int fd) +{ + struct sockaddr_in addr; + socklen_t len; + + g_return_val_if_fail(fd >= 0, 0); + + len = sizeof(addr); + if (getsockname(fd, (struct sockaddr *) &addr, &len) == -1) { + gaim_debug_warning("network", "getsockname: %s\n", strerror(errno)); + return 0; + } + + return ntohs(addr.sin_port); +} + +void +gaim_network_init(void) +{ + gaim_prefs_add_none ("/core/network"); + gaim_prefs_add_bool ("/core/network/auto_ip", TRUE); + gaim_prefs_add_string("/core/network/public_ip", ""); + gaim_prefs_add_bool ("/core/network/ports_range_use", FALSE); + gaim_prefs_add_int ("/core/network/ports_range_start", 1024); + gaim_prefs_add_int ("/core/network/ports_range_end", 2048); + + gaim_upnp_discover(NULL, NULL); +} diff -r d10dda2777a9 -r b63ebf84c42b core/network.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/network.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,191 @@ +/** + * @file network.h Network API + * @ingroup core + * + * 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 + */ +#ifndef _GAIM_NETWORK_H_ +#define _GAIM_NETWORK_H_ + +/* + * TODO: This API needs a way to cancel pending calls to + * gaim_network_listen_range() and company. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/**************************************************************************/ +/** @name Network API */ +/**************************************************************************/ +/*@{*/ + +typedef void (*GaimNetworkListenCallback) (int listenfd, gpointer data); + +/** + * Converts a dot-decimal IP address to an array of unsigned + * chars. For example, converts 192.168.0.1 to a 4 byte + * array containing 192, 168, 0 and 1. + * + * @param ip An IP address in dot-decimal notiation. + * @return An array of 4 bytes containing an IP addresses + * equivalent to the given parameter, or NULL if + * the given IP address is invalid. This value + * is statically allocated and should not be + * freed. + */ +const unsigned char *gaim_network_ip_atoi(const char *ip); + +/** + * Sets the IP address of the local system in preferences. This + * is the IP address that should be used for incoming connections + * (file transfer, direct IM, etc.) and should therefore be + * publicly accessible. + * + * @param ip The local IP address. + */ +void gaim_network_set_public_ip(const char *ip); + +/** + * Returns the IP address of the local system set in preferences. + * + * This returns the value set via gaim_network_set_public_ip(). + * You probably want to use gaim_network_get_my_ip() instead. + * + * @return The local IP address set in preferences. + */ +const char *gaim_network_get_public_ip(void); + +/** + * Returns the IP address of the local system. + * + * You probably want to use gaim_network_get_my_ip() instead. + * + * @note The returned string is a pointer to a static buffer. If this + * function is called twice, it may be important to make a copy + * of the returned string. + * + * @param fd The fd to use to help figure out the IP, or else -1. + * @return The local IP address. + */ +const char *gaim_network_get_local_system_ip(int fd); + +/** + * Returns the IP address that should be used anywhere a + * public IP addresses is needed (listening for an incoming + * file transfer, etc). + * + * If the user has manually specified an IP address via + * preferences, then this IP is returned. Otherwise the + * IP address returned by gaim_network_get_local_system_ip() + * is returned. + * + * @note The returned string is a pointer to a static buffer. If this + * function is called twice, it may be important to make a copy + * of the returned string. + * + * @param fd The fd to use to help figure out the IP, or -1. + * @return The local IP address to be used. + */ +const char *gaim_network_get_my_ip(int fd); + +/** + * Attempts to open a listening port ONLY on the specified port number. + * You probably want to use gaim_network_listen_range() instead of this. + * This function is useful, for example, if you wanted to write a telnet + * server as a Gaim plugin, and you HAD to listen on port 23. Why anyone + * would want to do that is beyond me. + * + * This opens a listening port. The caller will want to set up a watcher + * of type GAIM_INPUT_READ on the fd returned in cb. It will probably call + * accept in the watcher callback, and then possibly remove the watcher and close + * the listening socket, and add a new watcher on the new socket accept + * returned. + * + * @param port The port number to bind to. Must be greater than 0. + * @param socket_type The type of socket to open for listening. + * This will be either SOCK_STREAM for TCP or SOCK_DGRAM for UDP. + * @param cb The callback to be invoked when the port to listen on is available. + * The file descriptor of the listening socket will be specified in + * this callback, or -1 if no socket could be established. + * @param cb_data extra data to be returned when cb is called + * + * @return TRUE if the callback will be invoked, or FALSE if unable to obtain + * a local socket to listen on. + */ +gboolean gaim_network_listen(unsigned short port, int socket_type, + GaimNetworkListenCallback cb, gpointer cb_data); + +/** + * Opens a listening port selected from a range of ports. The range of + * ports used is chosen in the following manner: + * If a range is specified in preferences, these values are used. + * If a non-0 values are passed to the function as parameters, these + * values are used. + * Otherwise a port is chosen at random by the operating system. + * + * This opens a listening port. The caller will want to set up a watcher + * of type GAIM_INPUT_READ on the fd returned in cb. It will probably call + * accept in the watcher callback, and then possibly remove the watcher and close + * the listening socket, and add a new watcher on the new socket accept + * returned. + * + * @param start The port number to bind to, or 0 to pick a random port. + * Users are allowed to override this arg in prefs. + * @param end The highest possible port in the range of ports to listen on, + * or 0 to pick a random port. Users are allowed to override this + * arg in prefs. + * @param socket_type The type of socket to open for listening. + * This will be either SOCK_STREAM for TCP or SOCK_DGRAM for UDP. + * @param cb The callback to be invoked when the port to listen on is available. + * The file descriptor of the listening socket will be specified in + * this callback, or -1 if no socket could be established. + * @param cb_data extra data to be returned when cb is called + * + * @return TRUE if the callback will be invoked, or FALSE if unable to obtain + * a local socket to listen on. + */ +gboolean gaim_network_listen_range(unsigned short start, unsigned short end, + int socket_type, GaimNetworkListenCallback cb, gpointer cb_data); + +/** + * Gets a port number from a file descriptor. + * + * @param fd The file descriptor. This should be a tcp socket. The current + * implementation probably dies on anything but IPv4. Perhaps this + * possible bug will inspire new and valuable contributors to Gaim. + * @return The port number, in host byte order. + */ +unsigned short gaim_network_get_port_from_fd(int fd); + +/** + * Initializes the network subsystem. + */ +void gaim_network_init(void); + +/*@}*/ + +#ifdef __cplusplus +} +#endif + +#endif /* _GAIM_NETWORK_H_ */ diff -r d10dda2777a9 -r b63ebf84c42b core/notify.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/notify.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,593 @@ +/** + * @file notify.c Notification API + * @ingroup core + * + * 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 "notify.h" + +static GaimNotifyUiOps *notify_ui_ops = NULL; +static GList *handles = NULL; + +typedef struct +{ + GaimNotifyType type; + void *handle; + void *ui_handle; + GaimNotifyCloseCallback cb; + gpointer cb_user_data; +} GaimNotifyInfo; + +void * +gaim_notify_message(void *handle, GaimNotifyMsgType type, + const char *title, const char *primary, + const char *secondary, GaimNotifyCloseCallback cb, gpointer user_data) +{ + GaimNotifyUiOps *ops; + + g_return_val_if_fail(primary != NULL, NULL); + + ops = gaim_notify_get_ui_ops(); + + if (ops != NULL && ops->notify_message != NULL) { + GaimNotifyInfo *info; + + info = g_new0(GaimNotifyInfo, 1); + info->type = GAIM_NOTIFY_MESSAGE; + info->handle = handle; + info->ui_handle = ops->notify_message(type, title, primary, + secondary); + info->cb = cb; + info->cb_user_data = user_data; + + if (info->ui_handle != NULL) { + handles = g_list_append(handles, info); + + return info->ui_handle; + + } else { + if (info->cb != NULL) + info->cb(info->cb_user_data); + + g_free(info); + + return NULL; + } + + } else { + if (cb != NULL) + cb(user_data); + } + + return NULL; +} + +void * +gaim_notify_email(void *handle, const char *subject, const char *from, + const char *to, const char *url, GaimNotifyCloseCallback cb, + gpointer user_data) +{ + GaimNotifyUiOps *ops; + + ops = gaim_notify_get_ui_ops(); + + if (ops != NULL && ops->notify_email != NULL) { + GaimNotifyInfo *info; + + info = g_new0(GaimNotifyInfo, 1); + info->type = GAIM_NOTIFY_EMAIL; + info->handle = handle; + info->ui_handle = ops->notify_email(handle, subject, from, to, url); + info->cb = cb; + info->cb_user_data = user_data; + + if (info->ui_handle != NULL) { + handles = g_list_append(handles, info); + + return info->ui_handle; + + } else { + if (info->cb != NULL) + info->cb(info->cb_user_data); + + g_free(info); + + return NULL; + } + } else { + if (cb != NULL) + cb(user_data); + } + + return NULL; +} + +void * +gaim_notify_emails(void *handle, size_t count, gboolean detailed, + const char **subjects, const char **froms, + const char **tos, const char **urls, + GaimNotifyCloseCallback cb, gpointer user_data) +{ + GaimNotifyUiOps *ops; + + g_return_val_if_fail(count != 0, NULL); + + if (count == 1) { + return gaim_notify_email(handle, + (subjects == NULL ? NULL : *subjects), + (froms == NULL ? NULL : *froms), + (tos == NULL ? NULL : *tos), + (urls == NULL ? NULL : *urls), + cb, user_data); + } + + ops = gaim_notify_get_ui_ops(); + + if (ops != NULL && ops->notify_emails != NULL) { + GaimNotifyInfo *info; + + info = g_new0(GaimNotifyInfo, 1); + info->type = GAIM_NOTIFY_EMAILS; + info->handle = handle; + info->ui_handle = ops->notify_emails(handle, count, detailed, subjects, + froms, tos, urls); + info->cb = cb; + info->cb_user_data = user_data; + + if (info->ui_handle != NULL) { + handles = g_list_append(handles, info); + + return info->ui_handle; + + } else { + if (info->cb != NULL) + info->cb(info->cb_user_data); + + g_free(info); + + return NULL; + } + + } else { + if (cb != NULL) + cb(user_data); + } + + return NULL; +} + +void * +gaim_notify_formatted(void *handle, const char *title, const char *primary, + const char *secondary, const char *text, + GaimNotifyCloseCallback cb, gpointer user_data) +{ + GaimNotifyUiOps *ops; + + g_return_val_if_fail(primary != NULL, NULL); + + ops = gaim_notify_get_ui_ops(); + + if (ops != NULL && ops->notify_formatted != NULL) { + GaimNotifyInfo *info; + + info = g_new0(GaimNotifyInfo, 1); + info->type = GAIM_NOTIFY_FORMATTED; + info->handle = handle; + info->ui_handle = ops->notify_formatted(title, primary, secondary, text); + info->cb = cb; + info->cb_user_data = user_data; + + if (info->ui_handle != NULL) { + handles = g_list_append(handles, info); + + return info->ui_handle; + + } else { + if (info->cb != NULL) + info->cb(info->cb_user_data); + + g_free(info); + + return NULL; + } + + } else { + if (cb != NULL) + cb(user_data); + } + + return NULL; +} + +void * +gaim_notify_searchresults(GaimConnection *gc, const char *title, + const char *primary, const char *secondary, + GaimNotifySearchResults *results, GaimNotifyCloseCallback cb, + gpointer user_data) +{ + GaimNotifyUiOps *ops; + + ops = gaim_notify_get_ui_ops(); + + if (ops != NULL && ops->notify_searchresults != NULL) { + GaimNotifyInfo *info; + + info = g_new0(GaimNotifyInfo, 1); + info->type = GAIM_NOTIFY_SEARCHRESULTS; + info->handle = gc; + info->ui_handle = ops->notify_searchresults(gc, title, primary, + secondary, results, user_data); + info->cb = cb; + info->cb_user_data = user_data; + + if (info->ui_handle != NULL) { + handles = g_list_append(handles, info); + + return info->ui_handle; + + } else { + if (info->cb != NULL) + info->cb(info->cb_user_data); + + g_free(info); + + return NULL; + } + + } else { + if (cb != NULL) + cb(user_data); + } + + return NULL; +} + +void +gaim_notify_searchresults_free(GaimNotifySearchResults *results) +{ + GList *l; + + g_return_if_fail(results != NULL); + + for (l = results->buttons; l; l = g_list_delete_link(l, l)) { + GaimNotifySearchButton *button = l->data; + g_free(button->label); + g_free(button); + } + + for (l = results->rows; l; l = g_list_delete_link(l, l)) { + GList *row = l->data; + g_list_foreach(row, (GFunc)g_free, NULL); + g_list_free(row); + } + + for (l = results->columns; l; l = g_list_delete_link(l, l)) { + GaimNotifySearchColumn *column = l->data; + g_free(column->title); + g_free(column); + } + + g_free(results); +} + +void +gaim_notify_searchresults_new_rows(GaimConnection *gc, + GaimNotifySearchResults *results, + void *data) +{ + GaimNotifyUiOps *ops; + + ops = gaim_notify_get_ui_ops(); + + if (ops != NULL && ops->notify_searchresults != NULL) { + ops->notify_searchresults_new_rows(gc, results, data); + } +} + +void +gaim_notify_searchresults_button_add(GaimNotifySearchResults *results, + GaimNotifySearchButtonType type, + GaimNotifySearchResultsCallback cb) +{ + GaimNotifySearchButton *button; + + g_return_if_fail(results != NULL); + g_return_if_fail(cb != NULL); + + button = g_new0(GaimNotifySearchButton, 1); + button->callback = cb; + button->type = type; + + results->buttons = g_list_append(results->buttons, button); +} + + +void +gaim_notify_searchresults_button_add_labeled(GaimNotifySearchResults *results, + const char *label, + GaimNotifySearchResultsCallback cb) { + GaimNotifySearchButton *button; + + g_return_if_fail(results != NULL); + g_return_if_fail(cb != NULL); + g_return_if_fail(label != NULL); + g_return_if_fail(*label != '\0'); + + button = g_new0(GaimNotifySearchButton, 1); + button->callback = cb; + button->type = GAIM_NOTIFY_BUTTON_LABELED; + button->label = g_strdup(label); + + results->buttons = g_list_append(results->buttons, button); +} + + +GaimNotifySearchResults * +gaim_notify_searchresults_new() +{ + GaimNotifySearchResults *rs = g_new0(GaimNotifySearchResults, 1); + + return rs; +} + +void +gaim_notify_searchresults_column_add(GaimNotifySearchResults *results, + GaimNotifySearchColumn *column) +{ + g_return_if_fail(results != NULL); + g_return_if_fail(column != NULL); + + results->columns = g_list_append(results->columns, column); +} + +void gaim_notify_searchresults_row_add(GaimNotifySearchResults *results, + GList *row) +{ + g_return_if_fail(results != NULL); + g_return_if_fail(row != NULL); + + results->rows = g_list_append(results->rows, row); +} + +GaimNotifySearchColumn * +gaim_notify_searchresults_column_new(const char *title) +{ + GaimNotifySearchColumn *sc; + + g_return_val_if_fail(title != NULL, NULL); + + sc = g_new0(GaimNotifySearchColumn, 1); + sc->title = g_strdup(title); + + return sc; +} + +guint +gaim_notify_searchresults_get_columns_count(GaimNotifySearchResults *results) +{ + g_return_val_if_fail(results != NULL, 0); + + return g_list_length(results->columns); +} + +guint +gaim_notify_searchresults_get_rows_count(GaimNotifySearchResults *results) +{ + g_return_val_if_fail(results != NULL, 0); + + return g_list_length(results->rows); +} + +char * +gaim_notify_searchresults_column_get_title(GaimNotifySearchResults *results, + unsigned int column_id) +{ + g_return_val_if_fail(results != NULL, NULL); + + return ((GaimNotifySearchColumn *)g_list_nth_data(results->columns, column_id))->title; +} + +GList * +gaim_notify_searchresults_row_get(GaimNotifySearchResults *results, + unsigned int row_id) +{ + g_return_val_if_fail(results != NULL, NULL); + + return g_list_nth_data(results->rows, row_id); +} + +void * +gaim_notify_userinfo(GaimConnection *gc, const char *who, + const char *text, GaimNotifyCloseCallback cb, gpointer user_data) +{ + GaimNotifyUiOps *ops; + + g_return_val_if_fail(who != NULL, NULL); + + ops = gaim_notify_get_ui_ops(); + + if (ops != NULL && ops->notify_userinfo != NULL) { + GaimNotifyInfo *info; + char *infotext = g_strdup(text); + + info = g_new0(GaimNotifyInfo, 1); + info->type = GAIM_NOTIFY_USERINFO; + info->handle = gc; + + gaim_signal_emit(gaim_notify_get_handle(), "displaying-userinfo", + gaim_connection_get_account(gc), who, &infotext); + + info->ui_handle = ops->notify_userinfo(gc, who, infotext); + info->cb = cb; + info->cb_user_data = user_data; + + g_free(infotext); + + if (info->ui_handle != NULL) { + handles = g_list_append(handles, info); + + return info->ui_handle; + + } else { + if (info->cb != NULL) + info->cb(info->cb_user_data); + + g_free(info); + + return NULL; + } + + } else { + if (cb != NULL) + cb(user_data); + } + + return NULL; +} + +void * +gaim_notify_uri(void *handle, const char *uri) +{ + GaimNotifyUiOps *ops; + + g_return_val_if_fail(uri != NULL, NULL); + + ops = gaim_notify_get_ui_ops(); + + if (ops != NULL && ops->notify_uri != NULL) { + GaimNotifyInfo *info; + + info = g_new0(GaimNotifyInfo, 1); + info->type = GAIM_NOTIFY_URI; + info->handle = handle; + info->ui_handle = ops->notify_uri(uri); + + if (info->ui_handle != NULL) { + handles = g_list_append(handles, info); + + return info->ui_handle; + + } else { + g_free(info); + + return NULL; + } + } + + return NULL; +} + +void +gaim_notify_close(GaimNotifyType type, void *ui_handle) +{ + GList *l; + GaimNotifyUiOps *ops; + + g_return_if_fail(ui_handle != NULL); + + ops = gaim_notify_get_ui_ops(); + + for (l = handles; l != NULL; l = l->next) { + GaimNotifyInfo *info = l->data; + + if (info->ui_handle == ui_handle) { + handles = g_list_remove(handles, info); + + if (ops != NULL && ops->close_notify != NULL) + ops->close_notify(info->type, ui_handle); + + if (info->cb != NULL) + info->cb(info->cb_user_data); + + g_free(info); + + break; + } + } +} + +void +gaim_notify_close_with_handle(void *handle) +{ + GList *l, *l_next; + GaimNotifyUiOps *ops; + + g_return_if_fail(handle != NULL); + + ops = gaim_notify_get_ui_ops(); + + for (l = handles; l != NULL; l = l_next) { + GaimNotifyInfo *info = l->data; + + l_next = l->next; + + if (info->handle == handle) { + handles = g_list_remove(handles, info); + + if (ops != NULL && ops->close_notify != NULL) + ops->close_notify(info->type, info->ui_handle); + + if (info->cb != NULL) + info->cb(info->cb_user_data); + + g_free(info); + } + } +} + +void +gaim_notify_set_ui_ops(GaimNotifyUiOps *ops) +{ + notify_ui_ops = ops; +} + +GaimNotifyUiOps * +gaim_notify_get_ui_ops(void) +{ + return notify_ui_ops; +} + +void * +gaim_notify_get_handle(void) +{ + static int handle; + + return &handle; +} + +void +gaim_notify_init(void) +{ + gpointer handle = gaim_notify_get_handle(); + + gaim_signal_register(handle, "displaying-userinfo", + gaim_marshal_VOID__POINTER_POINTER_POINTER, NULL, 3, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_ACCOUNT), + gaim_value_new(GAIM_TYPE_STRING), + gaim_value_new_outgoing(GAIM_TYPE_STRING)); +} + +void +gaim_notify_uninit(void) +{ + gaim_signals_unregister_by_instance(gaim_notify_get_handle()); +} diff -r d10dda2777a9 -r b63ebf84c42b core/notify.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/notify.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,524 @@ +/** + * @file notify.h Notification API + * @ingroup core + * + * 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 + */ +#ifndef _GAIM_NOTIFY_H_ +#define _GAIM_NOTIFY_H_ + +#include +#include +#include + +#include "connection.h" + + +/** + * Notification close callbacks. + */ +typedef void (*GaimNotifyCloseCallback) (gpointer user_data); + + +/** + * Notification types. + */ +typedef enum +{ + GAIM_NOTIFY_MESSAGE = 0, /**< Message notification. */ + GAIM_NOTIFY_EMAIL, /**< Single e-mail notification. */ + GAIM_NOTIFY_EMAILS, /**< Multiple e-mail notification. */ + GAIM_NOTIFY_FORMATTED, /**< Formatted text. */ + GAIM_NOTIFY_SEARCHRESULTS, /**< Buddy search results. */ + GAIM_NOTIFY_USERINFO, /**< Formatted userinfo text. */ + GAIM_NOTIFY_URI /**< URI notification or display. */ + +} GaimNotifyType; + + +/** + * Notification message types. + */ +typedef enum +{ + GAIM_NOTIFY_MSG_ERROR = 0, /**< Error notification. */ + GAIM_NOTIFY_MSG_WARNING, /**< Warning notification. */ + GAIM_NOTIFY_MSG_INFO /**< Information notification. */ + +} GaimNotifyMsgType; + + +/** + * The types of buttons + */ +typedef enum +{ + GAIM_NOTIFY_BUTTON_LABELED = 0, /**< special use, see _button_add_labeled */ + GAIM_NOTIFY_BUTTON_CONTINUE = 1, + GAIM_NOTIFY_BUTTON_ADD, + GAIM_NOTIFY_BUTTON_INFO, + GAIM_NOTIFY_BUTTON_IM, + GAIM_NOTIFY_BUTTON_JOIN, + GAIM_NOTIFY_BUTTON_INVITE +} GaimNotifySearchButtonType; + + +/** + * Search results object. + */ +typedef struct +{ + GList *columns; /**< List of the search column objects. */ + GList *rows; /**< List of rows in the result. */ + GList *buttons; /**< List of buttons to display. */ + +} GaimNotifySearchResults; + + +/** + * Single column of a search result. + */ +typedef struct +{ + char *title; /**< Title of the column. */ + +} GaimNotifySearchColumn; + + +/** + * Callback for a button in a search result. + * + * @param c the GaimConnection passed to gaim_notify_searchresults + * @param row the contents of the selected row + * @param user_data User defined data. + */ +typedef void (*GaimNotifySearchResultsCallback)(GaimConnection *c, GList *row, + gpointer user_data); + + +/** + * Definition of a button. + */ +typedef struct +{ + GaimNotifySearchButtonType type; + GaimNotifySearchResultsCallback callback; /**< Function to be called when clicked. */ + char *label; /**< only for GAIM_NOTIFY_BUTTON_LABELED */ +} GaimNotifySearchButton; + + +/** + * Notification UI operations. + */ +typedef struct +{ + void *(*notify_message)(GaimNotifyMsgType type, const char *title, + const char *primary, const char *secondary); + + void *(*notify_email)(GaimConnection *gc, + const char *subject, const char *from, + const char *to, const char *url); + + void *(*notify_emails)(GaimConnection *gc, + size_t count, gboolean detailed, + const char **subjects, const char **froms, + const char **tos, const char **urls); + + void *(*notify_formatted)(const char *title, const char *primary, + const char *secondary, const char *text); + + void *(*notify_searchresults)(GaimConnection *gc, const char *title, + const char *primary, const char *secondary, + GaimNotifySearchResults *results, gpointer user_data); + + void (*notify_searchresults_new_rows)(GaimConnection *gc, + GaimNotifySearchResults *results, + void *data); + + void *(*notify_userinfo)(GaimConnection *gc, const char *who, + const char *text); + + void *(*notify_uri)(const char *uri); + + void (*close_notify)(GaimNotifyType type, void *ui_handle); + +} GaimNotifyUiOps; + + +#ifdef __cplusplus +extern "C" { +#endif + + +/**************************************************************************/ +/** Search results notification API */ +/**************************************************************************/ +/*@{*/ + +/** + * Displays results from a buddy search. This can be, for example, + * a window with a list of all found buddies, where you are given the + * option of adding buddies to your buddy list. + * + * @param gc The GaimConnection handle associated with the information. + * @param title The title of the message. If this is NULL, the title + * will be "Search Results." + * @param primary The main point of the message. + * @param secondary The secondary information. + * @param results The GaimNotifySearchResults instance. + * @param cb The callback to call when the user closes + * the notification. + * @param user_data The data to pass to the close callback and any other + * callback associated with a button. + * + * @return A UI-specific handle. + */ +void *gaim_notify_searchresults(GaimConnection *gc, const char *title, + const char *primary, const char *secondary, + GaimNotifySearchResults *results, GaimNotifyCloseCallback cb, + gpointer user_data); + +void gaim_notify_searchresults_free(GaimNotifySearchResults *results); + +/** + * Replace old rows with the new. Reuse an existing window. + * + * @param gc The GaimConnection structure. + * @param results The GaimNotifySearchResults structure. + * @param data Data returned by the gaim_notify_searchresults(). + */ +void gaim_notify_searchresults_new_rows(GaimConnection *gc, + GaimNotifySearchResults *results, + void *data); + + +/** + * Adds a stock button that will be displayed in the search results dialog. + * + * @param results The search results object. + * @param type Type of the button. (TODO: Only one button of a given type can be displayed.) + * @param cb Function that will be called on the click event. + */ +void gaim_notify_searchresults_button_add(GaimNotifySearchResults *results, + GaimNotifySearchButtonType type, + GaimNotifySearchResultsCallback cb); + + +/** + * Adds a plain labelled button that will be displayed in the search results dialog. + * + * @param results The search results object + * @param label The label to display + * @param cb Function that will be called on the click event + */ +void gaim_notify_searchresults_button_add_labeled(GaimNotifySearchResults *results, + const char *label, + GaimNotifySearchResultsCallback cb); + + +/** + * Returns a newly created search results object. + * + * @return The new search results object. + */ +GaimNotifySearchResults *gaim_notify_searchresults_new(void); + +/** + * Returns a newly created search result column object. + * + * @param title Title of the column. NOTE: Title will get g_strdup()ed. + * + * @return The new search column object. + */ +GaimNotifySearchColumn *gaim_notify_searchresults_column_new(const char *title); + +/** + * Adds a new column to the search result object. + * + * @param results The result object to which the column will be added. + * @param column The column that will be added to the result object. + */ +void gaim_notify_searchresults_column_add(GaimNotifySearchResults *results, + GaimNotifySearchColumn *column); + +/** + * Adds a new row of the results to the search results object. + * + * @param results The search results object. + * @param row The row of the results. + */ +void gaim_notify_searchresults_row_add(GaimNotifySearchResults *results, + GList *row); + +/** + * Returns a number of the rows in the search results object. + * + * @param results The search results object. + * + * @return Number of the result rows. + */ +guint gaim_notify_searchresults_get_rows_count(GaimNotifySearchResults *results); + +/** + * Returns a number of the columns in the search results object. + * + * @param results The search results object. + * + * @return Number of the columns. + */ +guint gaim_notify_searchresults_get_columns_count(GaimNotifySearchResults *results); + +/** + * Returns a row of the results from the search results object. + * + * @param results The search results object. + * @param row_id Index of the row to be returned. + * + * @return Row of the results. + */ +GList *gaim_notify_searchresults_row_get(GaimNotifySearchResults *results, + unsigned int row_id); + +/** + * Returns a title of the search results object's column. + * + * @param results The search results object. + * @param column_id Index of the column. + * + * @return Title of the column. + */ +char *gaim_notify_searchresults_column_get_title(GaimNotifySearchResults *results, + unsigned int column_id); + +/*@}*/ + +/**************************************************************************/ +/** @name Notification API */ +/**************************************************************************/ +/*@{*/ + +/** + * Displays a notification message to the user. + * + * @param handle The plugin or connection handle. + * @param type The notification type. + * @param title The title of the message. + * @param primary The main point of the message. + * @param secondary The secondary information. + * @param cb The callback to call when the user closes + * the notification. + * @param user_data The data to pass to the callback. + * + * @return A UI-specific handle. + */ +void *gaim_notify_message(void *handle, GaimNotifyMsgType type, + const char *title, const char *primary, + const char *secondary, GaimNotifyCloseCallback cb, + gpointer user_data); + +/** + * Displays a single e-mail notification to the user. + * + * @param handle The plugin or connection handle. + * @param subject The subject of the e-mail. + * @param from The from address. + * @param to The destination address. + * @param url The URL where the message can be read. + * @param cb The callback to call when the user closes + * the notification. + * @param user_data The data to pass to the callback. + * + * @return A UI-specific handle. + */ +void *gaim_notify_email(void *handle, const char *subject, + const char *from, const char *to, + const char *url, GaimNotifyCloseCallback cb, + gpointer user_data); + +/** + * Displays a notification for multiple e-mails to the user. + * + * @param handle The plugin or connection handle. + * @param count The number of e-mails. + * @param detailed @c TRUE if there is information for each e-mail in the + * arrays. + * @param subjects The array of subjects. + * @param froms The array of from addresses. + * @param tos The array of destination addresses. + * @param urls The URLs where the messages can be read. + * @param cb The callback to call when the user closes + * the notification. + * @param user_data The data to pass to the callback. + * + * @return A UI-specific handle. + */ +void *gaim_notify_emails(void *handle, size_t count, gboolean detailed, + const char **subjects, const char **froms, + const char **tos, const char **urls, + GaimNotifyCloseCallback cb, gpointer user_data); + +/** + * Displays a notification with formatted text. + * + * The text is essentially a stripped-down format of HTML, the same that + * IMs may send. + * + * @param handle The plugin or connection handle. + * @param title The title of the message. + * @param primary The main point of the message. + * @param secondary The secondary information. + * @param text The formatted text. + * @param cb The callback to call when the user closes + * the notification. + * @param user_data The data to pass to the callback. + * + * @return A UI-specific handle. + */ +void *gaim_notify_formatted(void *handle, const char *title, + const char *primary, const char *secondary, + const char *text, GaimNotifyCloseCallback cb, gpointer user_data); + +/** + * Displays user information with formatted text, passing information giving + * the connection and username from which the user information came. + * + * The text is essentially a stripped-down format of HTML, the same that + * IMs may send. + * + * @param gc The GaimConnection handle associated with the information. + * @param who The username associated with the information. + * @param text The formatted text. + * @param cb The callback to call when the user closes + * the notification. + * @param user_data The data to pass to the callback. + * + * @return A UI-specific handle. + */ +void *gaim_notify_userinfo(GaimConnection *gc, const char *who, + const char *text, GaimNotifyCloseCallback cb, + gpointer user_data); + +/** + * Opens a URI or somehow presents it to the user. + * + * @param handle The plugin or connection handle. + * @param uri The URI to display or go to. + * + * @return A UI-specific handle, if any. This may only be presented if + * the UI code displays a dialog instead of a webpage, or something + * similar. + */ +void *gaim_notify_uri(void *handle, const char *uri); + +/** + * Closes a notification. + * + * This should be used only by the UI operation functions and part of the + * core. + * + * @param type The notification type. + * @param ui_handle The notification UI handle. + */ +void gaim_notify_close(GaimNotifyType type, void *ui_handle); + +/** + * Closes all notifications registered with the specified handle. + * + * @param handle The handle. + */ +void gaim_notify_close_with_handle(void *handle); + +/** + * A wrapper for gaim_notify_message that displays an information message. + */ +#define gaim_notify_info(handle, title, primary, secondary) \ + gaim_notify_message((handle), GAIM_NOTIFY_MSG_INFO, (title), \ + (primary), (secondary), NULL, NULL) + +/** + * A wrapper for gaim_notify_message that displays a warning message. + */ +#define gaim_notify_warning(handle, title, primary, secondary) \ + gaim_notify_message((handle), GAIM_NOTIFY_MSG_WARNING, (title), \ + (primary), (secondary), NULL, NULL) + +/** + * A wrapper for gaim_notify_message that displays an error message. + */ +#define gaim_notify_error(handle, title, primary, secondary) \ + gaim_notify_message((handle), GAIM_NOTIFY_MSG_ERROR, (title), \ + (primary), (secondary), NULL, NULL) + +/*@}*/ + +/**************************************************************************/ +/** @name UI Registration Functions */ +/**************************************************************************/ +/*@{*/ + +/** + * Sets the UI operations structure to be used when displaying a + * notification. + * + * @param ops The UI operations structure. + */ +void gaim_notify_set_ui_ops(GaimNotifyUiOps *ops); + +/** + * Returns the UI operations structure to be used when displaying a + * notification. + * + * @return The UI operations structure. + */ +GaimNotifyUiOps *gaim_notify_get_ui_ops(void); + +/*@}*/ + +/**************************************************************************/ +/** @name Notify Subsystem */ +/**************************************************************************/ +/*@{*/ + +/** + * Returns the notify subsystem handle. + * + * @return The notify subsystem handle. + */ +void *gaim_notify_get_handle(void); + +/** + * Initializes the notify subsystem. + */ +void gaim_notify_init(void); + +/** + * Uninitializes the notify subsystem. + */ +void gaim_notify_uninit(void); + +/*@}*/ + + +#ifdef __cplusplus +} +#endif + +#endif /* _GAIM_NOTIFY_H_ */ diff -r d10dda2777a9 -r b63ebf84c42b core/ntlm.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/ntlm.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,360 @@ +/** + * @file ntlm.c + * + * gaim + * + * Copyright (C) 2005 Thomas Butter + * + * hashing done according to description of NTLM on + * http://www.innovation.ch/java/ntlm.html + * + * 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 +#include +#include "util.h" +#include "ntlm.h" +#include "cipher.h" +#include + +#define NTLM_NEGOTIATE_NTLM2_KEY 0x00080000 + +struct type1_message { + guint8 protocol[8]; /* 'N', 'T', 'L', 'M', 'S', 'S', 'P', '\0' */ + guint32 type; /* 0x00000001 */ + guint32 flags; /* 0x0000b203 */ + + short dom_len1; /* domain string length */ + short dom_len2; /* domain string length */ + guint32 dom_off; /* domain string offset */ + + short host_len1; /* host string length */ + short host_len2; /* host string length */ + guint32 host_off; /* host string offset (always 0x00000020) */ + +#if 0 + guint8 host[*]; /* host string (ASCII) */ + guint8 dom[*]; /* domain string (ASCII) */ +#endif +}; + +struct type2_message { + guint8 protocol[8]; /* 'N', 'T', 'L', 'M', 'S', 'S', 'P', '\0'*/ + guint32 type; /* 0x00000002 */ + + short msg_len1; /* target name length */ + short msg_len2; /* target name length */ + guint32 msg_off; /* target name offset (always 0x00000048) */ + + guint32 flags; /* 0x00008201 */ + + guint8 nonce[8]; /* nonce */ + guint8 context[8]; +}; + +struct type3_message { + guint8 protocol[8]; /* 'N', 'T', 'L', 'M', 'S', 'S', 'P', '\0'*/ + guint32 type; /* 0x00000003 */ + + short lm_resp_len1; /* LanManager response length (always 0x18)*/ + short lm_resp_len2; /* LanManager response length (always 0x18)*/ + guint32 lm_resp_off; /* LanManager response offset */ + + short nt_resp_len1; /* NT response length (always 0x18) */ + short nt_resp_len2; /* NT response length (always 0x18) */ + guint32 nt_resp_off; /* NT response offset */ + + short dom_len1; /* domain string length */ + short dom_len2; /* domain string length */ + guint32 dom_off; /* domain string offset (always 0x00000040) */ + + short user_len1; /* username string length */ + short user_len2; /* username string length */ + guint32 user_off; /* username string offset */ + + short host_len1; /* host string length */ + short host_len2; /* host string length */ + guint32 host_off; /* host string offset */ + + short sess_len1; + short sess_len2; + guint32 sess_off; /* message length */ + + guint32 flags; /* 0x00008201 */ + /* guint32 flags2; */ /* unknown, used in windows messenger */ + /* guint32 flags3; */ + +#if 0 + guint8 dom[*]; /* domain string (unicode UTF-16LE) */ + guint8 user[*]; /* username string (unicode UTF-16LE) */ + guint8 host[*]; /* host string (unicode UTF-16LE) */ + guint8 lm_resp[*]; /* LanManager response */ + guint8 nt_resp[*]; /* NT response */ +#endif +}; + +/* TODO: Will this work on both little-endian and big-endian machines? */ +gchar * +gaim_ntlm_gen_type1(const gchar *hostname, const gchar *domain) +{ + int hostnamelen; + int domainlen; + unsigned char *msg; + struct type1_message *tmsg; + gchar *tmp; + + hostnamelen = strlen(hostname); + domainlen = strlen(domain); + msg = g_malloc0(sizeof(struct type1_message) + hostnamelen + domainlen); + tmsg = (struct type1_message*)msg; + tmsg->protocol[0] = 'N'; + tmsg->protocol[1] = 'T'; + tmsg->protocol[2] = 'L'; + tmsg->protocol[3] = 'M'; + tmsg->protocol[4] = 'S'; + tmsg->protocol[5] = 'S'; + tmsg->protocol[6] = 'P'; + tmsg->protocol[7] = '\0'; + tmsg->type = 0x00000001; + tmsg->flags = 0x0000b202; + tmsg->dom_len1 = tmsg->dom_len2 = domainlen; + tmsg->dom_off = sizeof(struct type1_message) + hostnamelen; + tmsg->host_len1 = tmsg->host_len2 = hostnamelen; + tmsg->host_off = sizeof(struct type1_message); + memcpy(msg + tmsg->host_off, hostname, hostnamelen); + memcpy(msg + tmsg->dom_off, domain, domainlen); + + tmp = gaim_base64_encode(msg, sizeof(struct type1_message) + hostnamelen + domainlen); + g_free(msg); + + return tmp; +} + +guint8 * +gaim_ntlm_parse_type2(const gchar *type2, guint32 *flags) +{ + gsize retlen; + struct type2_message *tmsg; + static guint8 nonce[8]; + + tmsg = (struct type2_message*)gaim_base64_decode(type2, &retlen); + memcpy(nonce, tmsg->nonce, 8); + if (flags != NULL) + *flags = tmsg->flags; + g_free(tmsg); + + return nonce; +} + +/** + * Create a 64bit DES key by taking a 56bit key and adding + * a parity bit after every 7th bit. + */ +static void +setup_des_key(const guint8 key_56[], guint8 *key) +{ + key[0] = key_56[0]; + key[1] = ((key_56[0] << 7) & 0xFF) | (key_56[1] >> 1); + key[2] = ((key_56[1] << 6) & 0xFF) | (key_56[2] >> 2); + key[3] = ((key_56[2] << 5) & 0xFF) | (key_56[3] >> 3); + key[4] = ((key_56[3] << 4) & 0xFF) | (key_56[4] >> 4); + key[5] = ((key_56[4] << 3) & 0xFF) | (key_56[5] >> 5); + key[6] = ((key_56[5] << 2) & 0xFF) | (key_56[6] >> 6); + key[7] = (key_56[6] << 1) & 0xFF; +} + +/* + * helper function for gaim cipher.c + */ +static void +des_ecb_encrypt(const guint8 *plaintext, guint8 *result, const guint8 *key) +{ + GaimCipher *cipher; + GaimCipherContext *context; + gsize outlen; + + cipher = gaim_ciphers_find_cipher("des"); + context = gaim_cipher_context_new(cipher, NULL); + gaim_cipher_context_set_key(context, key); + gaim_cipher_context_encrypt(context, plaintext, 8, result, &outlen); + gaim_cipher_context_destroy(context); +} + +/* + * takes a 21 byte array and treats it as 3 56-bit DES keys. The + * 8 byte plaintext is encrypted with each key and the resulting 24 + * bytes are stored in the results array. + */ +static void +calc_resp(guint8 *keys, const guint8 *plaintext, unsigned char *results) +{ + guint8 key[8]; + setup_des_key(keys, key); + des_ecb_encrypt(plaintext, results, key); + + setup_des_key(keys + 7, key); + des_ecb_encrypt(plaintext, results + 8, key); + + setup_des_key(keys + 14, key); + des_ecb_encrypt(plaintext, results + 16, key); +} + +static void +gensesskey(char *buffer, const char *oldkey) +{ + int i = 0; + if(oldkey == NULL) { + for(i=0; i<16; i++) { + buffer[i] = (char)(rand() & 0xff); + } + } else { + memcpy(buffer, oldkey, 16); + } +} + +gchar * +gaim_ntlm_gen_type3(const gchar *username, const gchar *passw, const gchar *hostname, const gchar *domain, const guint8 *nonce, guint32 *flags) +{ + char lm_pw[14]; + unsigned char lm_hpw[21]; + char sesskey[16]; + guint8 key[8]; + int domainlen; + int usernamelen; + int hostnamelen; + int msglen; + struct type3_message *tmsg; + int passwlen, lennt; + unsigned char lm_resp[24], nt_resp[24]; + unsigned char magic[] = { 0x4B, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25 }; + unsigned char nt_hpw[21]; + char nt_pw[128]; + GaimCipher *cipher; + GaimCipherContext *context; + char *tmp; + int idx; + gchar *ucs2le; + + domainlen = strlen(domain) * 2; + usernamelen = strlen(username) * 2; + hostnamelen = strlen(hostname) * 2; + msglen = sizeof(struct type3_message) + domainlen + + usernamelen + hostnamelen + 0x18 + 0x18 + ((flags) ? 0x10 : 0); + tmsg = g_malloc0(msglen); + passwlen = strlen(passw); + + /* type3 message initialization */ + tmsg->protocol[0] = 'N'; + tmsg->protocol[1] = 'T'; + tmsg->protocol[2] = 'L'; + tmsg->protocol[3] = 'M'; + tmsg->protocol[4] = 'S'; + tmsg->protocol[5] = 'S'; + tmsg->protocol[6] = 'P'; + tmsg->type = 0x00000003; + tmsg->lm_resp_len1 = tmsg->lm_resp_len2 = 0x18; + tmsg->lm_resp_off = sizeof(struct type3_message) + domainlen + usernamelen + hostnamelen; + tmsg->nt_resp_len1 = tmsg->nt_resp_len2 = 0x18; + tmsg->nt_resp_off = sizeof(struct type3_message) + domainlen + usernamelen + hostnamelen + 0x18; + + tmsg->dom_len1 = tmsg->dom_len2 = domainlen; + tmsg->dom_off = sizeof(struct type3_message); + + tmsg->user_len1 = tmsg->user_len2 = usernamelen; + tmsg->user_off = sizeof(struct type3_message) + domainlen; + + tmsg->host_len1 = tmsg->host_len2 = hostnamelen; + tmsg->host_off = sizeof(struct type3_message) + domainlen + usernamelen; + + if(flags) { + tmsg->sess_off = sizeof(struct type3_message) + domainlen + usernamelen + hostnamelen + 0x18 + 0x18; + tmsg->sess_len1 = tmsg->sess_len2 = 0x0010; + } + + tmsg->flags = 0x00008200; + + tmp = (char *)tmsg + sizeof(struct type3_message); + + ucs2le = g_convert(domain, -1, "UCS-2LE", "UTF-8", NULL, NULL, NULL); + memcpy(tmp, ucs2le, domainlen); + g_free(ucs2le); + tmp += domainlen; + + ucs2le = g_convert(username, -1, "UCS-2LE", "UTF-8", NULL, NULL, NULL); + memcpy(tmp, ucs2le, usernamelen); + g_free(ucs2le); + tmp += usernamelen; + + ucs2le = g_convert(hostname, -1, "UCS-2LE", "UTF-8", NULL, NULL, NULL); + memcpy(tmp, ucs2le, hostnamelen); + g_free(ucs2le); + tmp += hostnamelen; + + /* LM */ + if (passwlen > 14) + passwlen = 14; + + for (idx = 0; idx < passwlen; idx++) + lm_pw[idx] = g_ascii_toupper(passw[idx]); + for (; idx < 14; idx++) + lm_pw[idx] = 0; + + setup_des_key((unsigned char*)lm_pw, key); + des_ecb_encrypt(magic, lm_hpw, key); + + setup_des_key((unsigned char*)(lm_pw + 7), key); + des_ecb_encrypt(magic, lm_hpw + 8, key); + + memset(lm_hpw + 16, 0, 5); + calc_resp(lm_hpw, nonce, lm_resp); + memcpy(tmp, lm_resp, 0x18); + tmp += 0x18; + + /* NTLM */ + /* Convert the password to UCS-2LE */ + lennt = strlen(passw); + for (idx = 0; idx < lennt; idx++) + { + nt_pw[2 * idx] = passw[idx]; + nt_pw[2 * idx + 1] = 0; + } + + cipher = gaim_ciphers_find_cipher("md4"); + context = gaim_cipher_context_new(cipher, NULL); + gaim_cipher_context_append(context, (guint8 *)nt_pw, 2 * lennt); + gaim_cipher_context_digest(context, 21, nt_hpw, NULL); + gaim_cipher_context_destroy(context); + + memset(nt_hpw + 16, 0, 5); + calc_resp(nt_hpw, nonce, nt_resp); + memcpy(tmp, nt_resp, 0x18); + tmp += 0x18; + + /* LCS Stuff */ + if (flags) { + tmsg->flags = 0x409082d4; + gensesskey(sesskey, NULL); + memcpy(tmp, sesskey, 0x10); + } + + /*tmsg->flags2 = 0x0a280105; + tmsg->flags3 = 0x0f000000;*/ + + tmp = gaim_base64_encode((guchar *)tmsg, msglen); + g_free(tmsg); + + return tmp; +} diff -r d10dda2777a9 -r b63ebf84c42b core/ntlm.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/ntlm.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,64 @@ +/** + * @file ntlm.h + * + * gaim + * + * Copyright (C) 2005, Thomas Butter + * + * ntlm structs are taken from NTLM description on + * http://www.innovation.ch/java/ntlm.html + * + * 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_NTLM_H +#define _GAIM_NTLM_H + +/** + * Generates the base64 encoded type 1 message needed for NTLM authentication + * + * @param hostname Your hostname + * @param domain The domain to authenticate to + * @return base64 encoded string to send to the server. This should + * be g_free'd by the caller. + */ +gchar *gaim_ntlm_gen_type1(const gchar *hostname, const gchar *domain); + +/** + * Parses the ntlm type 2 message + * + * @param type2 String containing the base64 encoded type2 message + * @param flags If not @c NULL, this will store the flags for the message + * + * @return The nonce for use in message type3. This is a statically + * allocated 8 byte binary string. + */ +guint8 *gaim_ntlm_parse_type2(const gchar *type2, guint32 *flags); + +/** + * Generates a type3 message + * + * @param username The username + * @param passw The password + * @param hostname The hostname + * @param domain The domain to authenticate against + * @param nonce The nonce returned by gaim_ntlm_parse_type2 + * @param flags Pointer to the flags returned by gaim_ntlm_parse_type2 + * @return A base64 encoded type3 message. This should be g_free'd by + * the caller. + */ +gchar *gaim_ntlm_gen_type3(const gchar *username, const gchar *passw, const gchar *hostname, const gchar *domain, const guint8 *nonce, guint32 *flags); + +#endif /* _GAIM_NTLM_H */ diff -r d10dda2777a9 -r b63ebf84c42b core/plugin.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugin.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,1587 @@ +/* + * 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 "accountopt.h" +#include "dbus-maybe.h" +#include "debug.h" +#include "notify.h" +#include "prefs.h" +#include "prpl.h" +#include "request.h" +#include "signals.h" +#include "util.h" +#include "version.h" + +typedef struct +{ + GHashTable *commands; + size_t command_count; + +} GaimPluginIpcInfo; + +typedef struct +{ + GaimCallback func; + GaimSignalMarshalFunc marshal; + + int num_params; + GaimValue **params; + GaimValue *ret_value; + +} GaimPluginIpcCommand; + +static GList *search_paths = NULL; +static GList *plugins = NULL; +static GList *loaded_plugins = NULL; +static GList *protocol_plugins = NULL; +#ifdef GAIM_PLUGINS +static GList *load_queue = NULL; +static GList *plugin_loaders = NULL; +#endif + +/* + * TODO: I think the intention was to allow multiple load and unload + * callback functions. Perhaps using a GList instead of a + * pointer to a single function. + */ +static void (*probe_cb)(void *) = NULL; +static void *probe_cb_data = NULL; +static void (*load_cb)(GaimPlugin *, void *) = NULL; +static void *load_cb_data = NULL; +static void (*unload_cb)(GaimPlugin *, void *) = NULL; +static void *unload_cb_data = NULL; + +#ifdef GAIM_PLUGINS + +static gboolean +has_file_extension(const char *filename, const char *ext) +{ + int len, extlen; + + if (filename == NULL || *filename == '\0' || ext == NULL) + return 0; + + extlen = strlen(ext); + len = strlen(filename) - extlen; + + if (len < 0) + return 0; + + return (strncmp(filename + len, ext, extlen) == 0); +} + +static gboolean +is_native(const char *filename) +{ + const char *last_period; + + last_period = strrchr(filename, '.'); + if (last_period == NULL) + return FALSE; + + return !(strcmp(last_period, ".dll") & + strcmp(last_period, ".sl") & + strcmp(last_period, ".so")); +} + +static char * +gaim_plugin_get_basename(const char *filename) +{ + const char *basename; + const char *last_period; + + basename = strrchr(filename, G_DIR_SEPARATOR); + if (basename != NULL) + basename++; + else + basename = filename; + + if (is_native(basename) && + ((last_period = strrchr(basename, '.')) != NULL)) + return g_strndup(basename, (last_period - basename)); + + return g_strdup(basename); +} + +static gboolean +loader_supports_file(GaimPlugin *loader, const char *filename) +{ + GList *exts; + + for (exts = GAIM_PLUGIN_LOADER_INFO(loader)->exts; exts != NULL; exts = exts->next) { + if (has_file_extension(filename, (char *)exts->data)) { + return TRUE; + } + } + + return FALSE; +} + +static GaimPlugin * +find_loader_for_plugin(const GaimPlugin *plugin) +{ + GaimPlugin *loader; + GList *l; + + if (plugin->path == NULL) + return NULL; + + for (l = gaim_plugins_get_loaded(); l != NULL; l = l->next) { + loader = l->data; + + if (loader->info->type == GAIM_PLUGIN_LOADER && + loader_supports_file(loader, plugin->path)) { + + return loader; + } + + loader = NULL; + } + + return NULL; +} + +#endif /* GAIM_PLUGINS */ + +/** + * Negative if a before b, 0 if equal, positive if a after b. + */ +static gint +compare_prpl(GaimPlugin *a, GaimPlugin *b) +{ + if(GAIM_IS_PROTOCOL_PLUGIN(a)) { + if(GAIM_IS_PROTOCOL_PLUGIN(b)) + return strcmp(a->info->name, b->info->name); + else + return -1; + } else { + if(GAIM_IS_PROTOCOL_PLUGIN(b)) + return 1; + else + return 0; + } +} + +GaimPlugin * +gaim_plugin_new(gboolean native, const char *path) +{ + GaimPlugin *plugin; + + plugin = g_new0(GaimPlugin, 1); + + plugin->native_plugin = native; + plugin->path = g_strdup(path); + + GAIM_DBUS_REGISTER_POINTER(plugin, GaimPlugin); + + return plugin; +} + +GaimPlugin * +gaim_plugin_probe(const char *filename) +{ +#ifdef GAIM_PLUGINS + GaimPlugin *plugin = NULL; + GaimPlugin *loader; + gpointer unpunned; + gchar *basename = NULL; + gboolean (*gaim_init_plugin)(GaimPlugin *); + + gaim_debug_misc("plugins", "probing %s\n", filename); + g_return_val_if_fail(filename != NULL, NULL); + + if (!g_file_test(filename, G_FILE_TEST_EXISTS)) + return NULL; + + /* If this plugin has already been probed then exit */ + basename = gaim_plugin_get_basename(filename); + plugin = gaim_plugins_find_with_basename(basename); + g_free(basename); + if (plugin != NULL) + { + if (!strcmp(filename, plugin->path)) + return plugin; + else if (!gaim_plugin_is_unloadable(plugin)) + { + gaim_debug_info("plugins", "Not loading %s. " + "Another plugin with the same name (%s) has already been loaded.\n", + filename, plugin->path); + return plugin; + } + else + { + /* The old plugin was a different file and it was unloadable. + * There's no guarantee that this new file with the same name + * will be loadable, but unless it fails in one of the silent + * ways and the first one didn't, it's not any worse. The user + * will still see a greyed-out plugin, which is what we want. */ + gaim_plugin_destroy(plugin); + } + } + + plugin = gaim_plugin_new(has_file_extension(filename, G_MODULE_SUFFIX), filename); + + if (plugin->native_plugin) { + const char *error; +#ifdef _WIN32 + /* Suppress error popups for failing to load plugins */ + UINT old_error_mode = SetErrorMode(SEM_FAILCRITICALERRORS); +#endif + + /* + * We pass G_MODULE_BIND_LOCAL here to prevent symbols from + * plugins being added to the global name space. + * + * G_MODULE_BIND_LOCAL was added in glib 2.3.3. + * TODO: I guess there's nothing we can do about that? + */ +#if GLIB_CHECK_VERSION(2,3,3) + plugin->handle = g_module_open(filename, G_MODULE_BIND_LOCAL); +#else + plugin->handle = g_module_open(filename, 0); +#endif + + if (plugin->handle == NULL) + { + const char *error = g_module_error(); + if (error != NULL && gaim_str_has_prefix(error, filename)) + { + error = error + strlen(filename); + + /* These are just so we don't crash. If we + * got this far, they should always be true. */ + if (*error == ':') + error++; + if (*error == ' ') + error++; + } + + if (error == NULL || !*error) + { + plugin->error = g_strdup(_("Unknown error")); + gaim_debug_error("plugins", "%s is unloadable: Unknown error\n", + plugin->path); + } + else + { + plugin->error = g_strdup(error); + gaim_debug_error("plugins", "%s is unloadable: %s\n", + plugin->path, plugin->error); + } +#if GLIB_CHECK_VERSION(2,3,3) + plugin->handle = g_module_open(filename, G_MODULE_BIND_LAZY | G_MODULE_BIND_LOCAL); +#else + plugin->handle = g_module_open(filename, G_MODULE_BIND_LAZY); +#endif + + if (plugin->handle == NULL) + { +#ifdef _WIN32 + /* Restore the original error mode */ + SetErrorMode(old_error_mode); +#endif + gaim_plugin_destroy(plugin); + return NULL; + } + else + { + /* We were able to load the plugin with lazy symbol binding. + * This means we're missing some symbol. Mark it as + * unloadable and keep going so we get the info to display + * to the user so they know to rebuild this plugin. */ + plugin->unloadable = TRUE; + } + } + + if (!g_module_symbol(plugin->handle, "gaim_init_plugin", + &unpunned)) + { + gaim_debug_error("plugins", "%s is not usable because the " + "'gaim_init_plugin' symbol could not be " + "found. Does the plugin call the " + "GAIM_INIT_PLUGIN() macro?\n", plugin->path); + + g_module_close(plugin->handle); + error = g_module_error(); + if (error != NULL) + gaim_debug_error("plugins", "Error closing module %s: %s\n", + plugin->path, error); + plugin->handle = NULL; + +#ifdef _WIN32 + /* Restore the original error mode */ + SetErrorMode(old_error_mode); +#endif + gaim_plugin_destroy(plugin); + return NULL; + } + gaim_init_plugin = unpunned; + +#ifdef _WIN32 + /* Restore the original error mode */ + SetErrorMode(old_error_mode); +#endif + } + else { + loader = find_loader_for_plugin(plugin); + + if (loader == NULL) { + gaim_plugin_destroy(plugin); + return NULL; + } + + gaim_init_plugin = GAIM_PLUGIN_LOADER_INFO(loader)->probe; + } + + if (!gaim_init_plugin(plugin) || plugin->info == NULL) + { + gaim_plugin_destroy(plugin); + return NULL; + } + + /* Really old plugins. */ + if (plugin->info->magic != GAIM_PLUGIN_MAGIC) + { + if (plugin->info->magic >= 2 && plugin->info->magic <= 4) + { + struct _GaimPluginInfo2 + { + unsigned int api_version; + GaimPluginType type; + char *ui_requirement; + unsigned long flags; + GList *dependencies; + GaimPluginPriority priority; + + char *id; + char *name; + char *version; + char *summary; + char *description; + char *author; + char *homepage; + + gboolean (*load)(GaimPlugin *plugin); + gboolean (*unload)(GaimPlugin *plugin); + void (*destroy)(GaimPlugin *plugin); + + void *ui_info; + void *extra_info; + GaimPluginUiInfo *prefs_info; + GList *(*actions)(GaimPlugin *plugin, gpointer context); + } *info2 = (struct _GaimPluginInfo2 *)plugin->info; + + /* This leaks... but only for ancient plugins, so deal with it. */ + plugin->info = g_new0(GaimPluginInfo, 1); + + /* We don't really need all these to display the plugin info, but + * I'm copying them all for good measure. */ + plugin->info->magic = info2->api_version; + plugin->info->type = info2->type; + plugin->info->ui_requirement = info2->ui_requirement; + plugin->info->flags = info2->flags; + plugin->info->dependencies = info2->dependencies; + plugin->info->id = info2->id; + plugin->info->name = info2->name; + plugin->info->version = info2->version; + plugin->info->summary = info2->summary; + plugin->info->description = info2->description; + plugin->info->author = info2->author; + plugin->info->homepage = info2->homepage; + plugin->info->load = info2->load; + plugin->info->unload = info2->unload; + plugin->info->destroy = info2->destroy; + plugin->info->ui_info = info2->ui_info; + plugin->info->extra_info = info2->extra_info; + + if (info2->api_version >= 3) + plugin->info->prefs_info = info2->prefs_info; + + if (info2->api_version >= 4) + plugin->info->actions = info2->actions; + + + plugin->error = g_strdup_printf(_("Plugin magic mismatch %d (need %d)"), + plugin->info->magic, GAIM_PLUGIN_MAGIC); + gaim_debug_error("plugins", "%s is unloadable: Plugin magic mismatch %d (need %d)\n", + plugin->path, plugin->info->magic, GAIM_PLUGIN_MAGIC); + plugin->unloadable = TRUE; + return plugin; + } + + gaim_debug_error("plugins", "%s is unloadable: Plugin magic mismatch %d (need %d)\n", + plugin->path, plugin->info->magic, GAIM_PLUGIN_MAGIC); + gaim_plugin_destroy(plugin); + return NULL; + } + + if (plugin->info->major_version != GAIM_MAJOR_VERSION || + plugin->info->minor_version > GAIM_MINOR_VERSION) + { + plugin->error = g_strdup_printf(_("ABI version mismatch %d.%d.x (need %d.%d.x)"), + plugin->info->major_version, plugin->info->minor_version, + GAIM_MAJOR_VERSION, GAIM_MINOR_VERSION); + gaim_debug_error("plugins", "%s is unloadable: ABI version mismatch %d.%d.x (need %d.%d.x)\n", + plugin->path, plugin->info->major_version, plugin->info->minor_version, + GAIM_MAJOR_VERSION, GAIM_MINOR_VERSION); + plugin->unloadable = TRUE; + return plugin; + } + + if (plugin->info->type == GAIM_PLUGIN_PROTOCOL) + { + /* If plugin is a PRPL, make sure it implements the required functions */ + if ((GAIM_PLUGIN_PROTOCOL_INFO(plugin)->list_icon == NULL) || + (GAIM_PLUGIN_PROTOCOL_INFO(plugin)->login == NULL) || + (GAIM_PLUGIN_PROTOCOL_INFO(plugin)->close == NULL)) + { + plugin->error = g_strdup(_("Plugin does not implement all required functions")); + gaim_debug_error("plugins", "%s is unloadable: Plugin does not implement all required functions\n", + plugin->path); + plugin->unloadable = TRUE; + return plugin; + } + + /* For debugging, let's warn about prpl prefs. */ + if (plugin->info->prefs_info != NULL) + { + gaim_debug_error("plugins", "%s has a prefs_info, but is a prpl. This is no longer supported.\n", + plugin->path); + } + } + + return plugin; +#else + return NULL; +#endif /* !GAIM_PLUGINS */ +} + +#ifdef GAIM_PLUGINS +static gint +compare_plugins(gconstpointer a, gconstpointer b) +{ + const GaimPlugin *plugina = a; + const GaimPlugin *pluginb = b; + + return strcmp(plugina->info->name, pluginb->info->name); +} +#endif /* GAIM_PLUGINS */ + +gboolean +gaim_plugin_load(GaimPlugin *plugin) +{ +#ifdef GAIM_PLUGINS + GList *dep_list = NULL; + GList *l; + + g_return_val_if_fail(plugin != NULL, FALSE); + + if (gaim_plugin_is_loaded(plugin)) + return TRUE; + + if (gaim_plugin_is_unloadable(plugin)) + return FALSE; + + g_return_val_if_fail(plugin->error == NULL, FALSE); + + /* + * Go through the list of the plugin's dependencies. + * + * First pass: Make sure all the plugins needed are probed. + */ + for (l = plugin->info->dependencies; l != NULL; l = l->next) + { + const char *dep_name = (const char *)l->data; + GaimPlugin *dep_plugin; + + dep_plugin = gaim_plugins_find_with_id(dep_name); + + if (dep_plugin == NULL) + { + char *tmp; + + tmp = g_strdup_printf(_("The required plugin %s was not found. " + "Please install this plugin and try again."), + dep_name); + + gaim_notify_error(NULL, NULL, + _("Gaim encountered errors loading the plugin."), tmp); + g_free(tmp); + + g_list_free(dep_list); + + return FALSE; + } + + dep_list = g_list_append(dep_list, dep_plugin); + } + + /* Second pass: load all the required plugins. */ + for (l = dep_list; l != NULL; l = l->next) + { + GaimPlugin *dep_plugin = (GaimPlugin *)l->data; + + if (!gaim_plugin_is_loaded(dep_plugin)) + { + if (!gaim_plugin_load(dep_plugin)) + { + char *tmp; + + tmp = g_strdup_printf(_("The required plugin %s was unable to load."), + plugin->info->name); + + gaim_notify_error(NULL, NULL, + _("Gaim was unable to load your plugin."), tmp); + g_free(tmp); + + g_list_free(dep_list); + + return FALSE; + } + } + } + + /* Third pass: note that other plugins are dependencies of this plugin. + * This is done separately in case we had to bail out earlier. */ + for (l = dep_list; l != NULL; l = l->next) + { + GaimPlugin *dep_plugin = (GaimPlugin *)l->data; + dep_plugin->dependent_plugins = g_list_prepend(dep_plugin->dependent_plugins, plugin->info->id); + } + + g_list_free(dep_list); + + if (plugin->native_plugin) + { + if (plugin->info != NULL && plugin->info->load != NULL) + { + if (!plugin->info->load(plugin)) + return FALSE; + } + } + else { + GaimPlugin *loader; + GaimPluginLoaderInfo *loader_info; + + loader = find_loader_for_plugin(plugin); + + if (loader == NULL) + return FALSE; + + loader_info = GAIM_PLUGIN_LOADER_INFO(loader); + + if (loader_info->load != NULL) + { + if (!loader_info->load(plugin)) + return FALSE; + } + } + + loaded_plugins = g_list_insert_sorted(loaded_plugins, plugin, compare_plugins); + + plugin->loaded = TRUE; + + /* TODO */ + if (load_cb != NULL) + load_cb(plugin, load_cb_data); + + gaim_signal_emit(gaim_plugins_get_handle(), "plugin-load", plugin); + + return TRUE; + +#else + return TRUE; +#endif /* !GAIM_PLUGINS */ +} + +gboolean +gaim_plugin_unload(GaimPlugin *plugin) +{ +#ifdef GAIM_PLUGINS + GList *l; + + g_return_val_if_fail(plugin != NULL, FALSE); + + loaded_plugins = g_list_remove(loaded_plugins, plugin); + if ((plugin->info != NULL) && GAIM_IS_PROTOCOL_PLUGIN(plugin)) + protocol_plugins = g_list_remove(protocol_plugins, plugin); + + g_return_val_if_fail(gaim_plugin_is_loaded(plugin), FALSE); + + gaim_debug_info("plugins", "Unloading plugin %s\n", plugin->info->name); + + /* cancel any pending dialogs the plugin has */ + gaim_request_close_with_handle(plugin); + gaim_notify_close_with_handle(plugin); + + plugin->loaded = FALSE; + + /* Unload all plugins that depend on this plugin. */ + while ((l = plugin->dependent_plugins) != NULL) + { + const char * dep_name = (const char *)l->data; + GaimPlugin *dep_plugin; + + dep_plugin = gaim_plugins_find_with_id(dep_name); + + if (dep_plugin != NULL && gaim_plugin_is_loaded(dep_plugin)) + { + if (!gaim_plugin_unload(dep_plugin)) + { + char *translated_name = g_strdup(_(dep_plugin->info->name)); + char *tmp; + + tmp = g_strdup_printf(_("The dependent plugin %s failed to unload."), + translated_name); + g_free(translated_name); + + gaim_notify_error(NULL, NULL, + _("Gaim encountered errors unloading the plugin."), tmp); + g_free(tmp); + } + } + } + + /* Remove this plugin from each dependency's dependent_plugins list. */ + for (l = plugin->info->dependencies; l != NULL; l = l->next) + { + const char *dep_name = (const char *)l->data; + GaimPlugin *dependency; + + dependency = gaim_plugins_find_with_id(dep_name); + + dependency->dependent_plugins = g_list_remove(dependency->dependent_plugins, plugin->info->id); + } + + if (plugin->native_plugin) { + if (plugin->info->unload != NULL) + plugin->info->unload(plugin); + + if (plugin->info->type == GAIM_PLUGIN_PROTOCOL) { + GaimPluginProtocolInfo *prpl_info; + GList *l; + + prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(plugin); + + for (l = prpl_info->user_splits; l != NULL; l = l->next) + gaim_account_user_split_destroy(l->data); + + for (l = prpl_info->protocol_options; l != NULL; l = l->next) + gaim_account_option_destroy(l->data); + + if (prpl_info->user_splits != NULL) { + g_list_free(prpl_info->user_splits); + prpl_info->user_splits = NULL; + } + + if (prpl_info->protocol_options != NULL) { + g_list_free(prpl_info->protocol_options); + prpl_info->protocol_options = NULL; + } + } + } + else { + GaimPlugin *loader; + GaimPluginLoaderInfo *loader_info; + + loader = find_loader_for_plugin(plugin); + + if (loader == NULL) + return FALSE; + + loader_info = GAIM_PLUGIN_LOADER_INFO(loader); + + if (loader_info->unload != NULL) + loader_info->unload(plugin); + } + + gaim_signals_disconnect_by_handle(plugin); + gaim_plugin_ipc_unregister_all(plugin); + + /* TODO */ + if (unload_cb != NULL) + unload_cb(plugin, unload_cb_data); + + gaim_signal_emit(gaim_plugins_get_handle(), "plugin-unload", plugin); + + gaim_prefs_disconnect_by_handle(plugin); + + return TRUE; +#else + return TRUE; +#endif /* GAIM_PLUGINS */ +} + +gboolean +gaim_plugin_reload(GaimPlugin *plugin) +{ +#ifdef GAIM_PLUGINS + g_return_val_if_fail(plugin != NULL, FALSE); + g_return_val_if_fail(gaim_plugin_is_loaded(plugin), FALSE); + + if (!gaim_plugin_unload(plugin)) + return FALSE; + + if (!gaim_plugin_load(plugin)) + return FALSE; + + return TRUE; +#else + return TRUE; +#endif /* !GAIM_PLUGINS */ +} + +void +gaim_plugin_destroy(GaimPlugin *plugin) +{ +#ifdef GAIM_PLUGINS + g_return_if_fail(plugin != NULL); + + if (gaim_plugin_is_loaded(plugin)) + gaim_plugin_unload(plugin); + + plugins = g_list_remove(plugins, plugin); + + if (load_queue != NULL) + load_queue = g_list_remove(load_queue, plugin); + + /* true, this may leak a little memory if there is a major version + * mismatch, but it's a lot better than trying to free something + * we shouldn't, and crashing while trying to load an old plugin */ + if(plugin->info == NULL || plugin->info->magic != GAIM_PLUGIN_MAGIC || + plugin->info->major_version != GAIM_MAJOR_VERSION) + { + if(plugin->handle) + g_module_close(plugin->handle); + + g_free(plugin->path); + g_free(plugin->error); + + GAIM_DBUS_UNREGISTER_POINTER(plugin); + + g_free(plugin); + return; + } + + if (plugin->info != NULL) + g_list_free(plugin->info->dependencies); + + if (plugin->native_plugin) + { + if (plugin->info != NULL && plugin->info->type == GAIM_PLUGIN_LOADER) + { + GaimPluginLoaderInfo *loader_info; + GList *exts, *l, *next_l; + GaimPlugin *p2; + + loader_info = GAIM_PLUGIN_LOADER_INFO(plugin); + + if (loader_info != NULL && loader_info->exts != NULL) + { + for (exts = GAIM_PLUGIN_LOADER_INFO(plugin)->exts; + exts != NULL; + exts = exts->next) { + + for (l = gaim_plugins_get_all(); l != NULL; l = next_l) + { + next_l = l->next; + + p2 = l->data; + + if (p2->path != NULL && + has_file_extension(p2->path, exts->data)) + { + gaim_plugin_destroy(p2); + } + } + } + + g_list_free(loader_info->exts); + } + + plugin_loaders = g_list_remove(plugin_loaders, plugin); + } + + if (plugin->info != NULL && plugin->info->destroy != NULL) + plugin->info->destroy(plugin); + + if (plugin->handle != NULL) + g_module_close(plugin->handle); + } + else + { + GaimPlugin *loader; + GaimPluginLoaderInfo *loader_info; + + loader = find_loader_for_plugin(plugin); + + if (loader != NULL) + { + loader_info = GAIM_PLUGIN_LOADER_INFO(loader); + + if (loader_info->destroy != NULL) + loader_info->destroy(plugin); + } + } + + g_free(plugin->path); + g_free(plugin->error); + + GAIM_DBUS_UNREGISTER_POINTER(plugin); + + g_free(plugin); +#endif /* !GAIM_PLUGINS */ +} + +gboolean +gaim_plugin_is_loaded(const GaimPlugin *plugin) +{ + g_return_val_if_fail(plugin != NULL, FALSE); + + return plugin->loaded; +} + +gboolean +gaim_plugin_is_unloadable(const GaimPlugin *plugin) +{ + g_return_val_if_fail(plugin != NULL, FALSE); + + return plugin->unloadable; +} + +const gchar * +gaim_plugin_get_id(const GaimPlugin *plugin) { + g_return_val_if_fail(plugin, NULL); + g_return_val_if_fail(plugin->info, NULL); + + return plugin->info->id; +} + +const gchar * +gaim_plugin_get_name(const GaimPlugin *plugin) { + g_return_val_if_fail(plugin, NULL); + g_return_val_if_fail(plugin->info, NULL); + + return plugin->info->name; +} + +const gchar * +gaim_plugin_get_version(const GaimPlugin *plugin) { + g_return_val_if_fail(plugin, NULL); + g_return_val_if_fail(plugin->info, NULL); + + return plugin->info->version; +} + +const gchar * +gaim_plugin_get_summary(const GaimPlugin *plugin) { + g_return_val_if_fail(plugin, NULL); + g_return_val_if_fail(plugin->info, NULL); + + return plugin->info->summary; +} + +const gchar * +gaim_plugin_get_description(const GaimPlugin *plugin) { + g_return_val_if_fail(plugin, NULL); + g_return_val_if_fail(plugin->info, NULL); + + return plugin->info->description; +} + +const gchar * +gaim_plugin_get_author(const GaimPlugin *plugin) { + g_return_val_if_fail(plugin, NULL); + g_return_val_if_fail(plugin->info, NULL); + + return plugin->info->author; +} + +const gchar * +gaim_plugin_get_homepage(const GaimPlugin *plugin) { + g_return_val_if_fail(plugin, NULL); + g_return_val_if_fail(plugin->info, NULL); + + return plugin->info->homepage; +} + +/************************************************************************** + * Plugin IPC + **************************************************************************/ +static void +destroy_ipc_info(void *data) +{ + GaimPluginIpcCommand *ipc_command = (GaimPluginIpcCommand *)data; + int i; + + if (ipc_command->params != NULL) + { + for (i = 0; i < ipc_command->num_params; i++) + gaim_value_destroy(ipc_command->params[i]); + + g_free(ipc_command->params); + } + + if (ipc_command->ret_value != NULL) + gaim_value_destroy(ipc_command->ret_value); + + g_free(ipc_command); +} + +gboolean +gaim_plugin_ipc_register(GaimPlugin *plugin, const char *command, + GaimCallback func, GaimSignalMarshalFunc marshal, + GaimValue *ret_value, int num_params, ...) +{ + GaimPluginIpcInfo *ipc_info; + GaimPluginIpcCommand *ipc_command; + + g_return_val_if_fail(plugin != NULL, FALSE); + g_return_val_if_fail(command != NULL, FALSE); + g_return_val_if_fail(func != NULL, FALSE); + g_return_val_if_fail(marshal != NULL, FALSE); + + if (plugin->ipc_data == NULL) + { + ipc_info = plugin->ipc_data = g_new0(GaimPluginIpcInfo, 1); + ipc_info->commands = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, destroy_ipc_info); + } + else + ipc_info = (GaimPluginIpcInfo *)plugin->ipc_data; + + ipc_command = g_new0(GaimPluginIpcCommand, 1); + ipc_command->func = func; + ipc_command->marshal = marshal; + ipc_command->num_params = num_params; + ipc_command->ret_value = ret_value; + + if (num_params > 0) + { + va_list args; + int i; + + ipc_command->params = g_new0(GaimValue *, num_params); + + va_start(args, num_params); + + for (i = 0; i < num_params; i++) + ipc_command->params[i] = va_arg(args, GaimValue *); + + va_end(args); + } + + g_hash_table_replace(ipc_info->commands, g_strdup(command), ipc_command); + + ipc_info->command_count++; + + return TRUE; +} + +void +gaim_plugin_ipc_unregister(GaimPlugin *plugin, const char *command) +{ + GaimPluginIpcInfo *ipc_info; + + g_return_if_fail(plugin != NULL); + g_return_if_fail(command != NULL); + + ipc_info = (GaimPluginIpcInfo *)plugin->ipc_data; + + if (ipc_info == NULL || + g_hash_table_lookup(ipc_info->commands, command) == NULL) + { + gaim_debug_error("plugins", + "IPC command '%s' was not registered for plugin %s\n", + command, plugin->info->name); + return; + } + + g_hash_table_remove(ipc_info->commands, command); + + ipc_info->command_count--; + + if (ipc_info->command_count == 0) + { + g_hash_table_destroy(ipc_info->commands); + g_free(ipc_info); + + plugin->ipc_data = NULL; + } +} + +void +gaim_plugin_ipc_unregister_all(GaimPlugin *plugin) +{ + GaimPluginIpcInfo *ipc_info; + + g_return_if_fail(plugin != NULL); + + if (plugin->ipc_data == NULL) + return; /* Silently ignore it. */ + + ipc_info = (GaimPluginIpcInfo *)plugin->ipc_data; + + g_hash_table_destroy(ipc_info->commands); + g_free(ipc_info); + + plugin->ipc_data = NULL; +} + +gboolean +gaim_plugin_ipc_get_params(GaimPlugin *plugin, const char *command, + GaimValue **ret_value, int *num_params, + GaimValue ***params) +{ + GaimPluginIpcInfo *ipc_info; + GaimPluginIpcCommand *ipc_command; + + g_return_val_if_fail(plugin != NULL, FALSE); + g_return_val_if_fail(command != NULL, FALSE); + + ipc_info = (GaimPluginIpcInfo *)plugin->ipc_data; + + if (ipc_info == NULL || + (ipc_command = g_hash_table_lookup(ipc_info->commands, + command)) == NULL) + { + gaim_debug_error("plugins", + "IPC command '%s' was not registered for plugin %s\n", + command, plugin->info->name); + + return FALSE; + } + + if (num_params != NULL) + *num_params = ipc_command->num_params; + + if (params != NULL) + *params = ipc_command->params; + + if (ret_value != NULL) + *ret_value = ipc_command->ret_value; + + return TRUE; +} + +void * +gaim_plugin_ipc_call(GaimPlugin *plugin, const char *command, + gboolean *ok, ...) +{ + GaimPluginIpcInfo *ipc_info; + GaimPluginIpcCommand *ipc_command; + va_list args; + void *ret_value; + + if (ok != NULL) + *ok = FALSE; + + g_return_val_if_fail(plugin != NULL, NULL); + g_return_val_if_fail(command != NULL, NULL); + + ipc_info = (GaimPluginIpcInfo *)plugin->ipc_data; + + if (ipc_info == NULL || + (ipc_command = g_hash_table_lookup(ipc_info->commands, + command)) == NULL) + { + gaim_debug_error("plugins", + "IPC command '%s' was not registered for plugin %s\n", + command, plugin->info->name); + + return NULL; + } + + va_start(args, ok); + ipc_command->marshal(ipc_command->func, args, NULL, &ret_value); + va_end(args); + + if (ok != NULL) + *ok = TRUE; + + return ret_value; +} + +/************************************************************************** + * Plugins subsystem + **************************************************************************/ +void * +gaim_plugins_get_handle(void) { + static int handle; + + return &handle; +} + +void +gaim_plugins_init(void) { + void *handle = gaim_plugins_get_handle(); + + gaim_signal_register(handle, "plugin-load", + gaim_marshal_VOID__POINTER, + NULL, 1, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_PLUGIN)); + gaim_signal_register(handle, "plugin-unload", + gaim_marshal_VOID__POINTER, + NULL, 1, + gaim_value_new(GAIM_TYPE_SUBTYPE, + GAIM_SUBTYPE_PLUGIN)); +} + +void +gaim_plugins_uninit(void) { + gaim_signals_disconnect_by_handle(gaim_plugins_get_handle()); +} + +/************************************************************************** + * Plugins API + **************************************************************************/ +void +gaim_plugins_add_search_path(const char *path) +{ + g_return_if_fail(path != NULL); + + if (g_list_find_custom(search_paths, path, (GCompareFunc)strcmp)) + return; + + search_paths = g_list_append(search_paths, strdup(path)); +} + +void +gaim_plugins_unload_all(void) +{ +#ifdef GAIM_PLUGINS + + while (loaded_plugins != NULL) + gaim_plugin_unload(loaded_plugins->data); + +#endif /* GAIM_PLUGINS */ +} + +void +gaim_plugins_destroy_all(void) +{ +#ifdef GAIM_PLUGINS + + while (plugins != NULL) + gaim_plugin_destroy(plugins->data); + +#endif /* GAIM_PLUGINS */ +} + +void +gaim_plugins_load_saved(const char *key) +{ +#ifdef GAIM_PLUGINS + GList *f, *files; + + g_return_if_fail(key != NULL); + + files = gaim_prefs_get_string_list(key); + + for (f = files; f; f = f->next) + { + char *filename; + char *basename; + GaimPlugin *plugin; + + if (f->data == NULL) + continue; + + filename = f->data; + + /* + * We don't know if the filename uses Windows or Unix path + * separators (because people might be sharing a prefs.xml + * file across systems), so we find the last occurrence + * of either. + */ + basename = strrchr(filename, '/'); + if ((basename == NULL) || (basename < strrchr(filename, '\\'))) + basename = strrchr(filename, '\\'); + if (basename != NULL) + basename++; + + /* Strip the extension */ + if (basename) + basename = gaim_plugin_get_basename(filename); + + if ((plugin = gaim_plugins_find_with_filename(filename)) != NULL) + { + gaim_debug_info("plugins", "Loading saved plugin %s\n", + plugin->path); + gaim_plugin_load(plugin); + } + else if (basename && (plugin = gaim_plugins_find_with_basename(basename)) != NULL) + { + gaim_debug_info("plugins", "Loading saved plugin %s\n", + plugin->path); + gaim_plugin_load(plugin); + } + else + { + gaim_debug_error("plugins", "Unable to find saved plugin %s\n", + filename); + } + + g_free(basename); + + g_free(f->data); + } + + g_list_free(files); +#endif /* GAIM_PLUGINS */ +} + + +void +gaim_plugins_probe(const char *ext) +{ +#ifdef GAIM_PLUGINS + GDir *dir; + const gchar *file; + gchar *path; + GaimPlugin *plugin; + GList *cur; + const char *search_path; + + if (!g_module_supported()) + return; + + /* Probe plugins */ + for (cur = search_paths; cur != NULL; cur = cur->next) + { + search_path = cur->data; + + dir = g_dir_open(search_path, 0, NULL); + + if (dir != NULL) + { + while ((file = g_dir_read_name(dir)) != NULL) + { + path = g_build_filename(search_path, file, NULL); + + if (ext == NULL || has_file_extension(file, ext)) + plugin = gaim_plugin_probe(path); + + g_free(path); + } + + g_dir_close(dir); + } + } + + /* See if we have any plugins waiting to load */ + while (load_queue != NULL) + { + plugin = (GaimPlugin *)load_queue->data; + + load_queue = g_list_remove(load_queue, plugin); + + if (plugin == NULL || plugin->info == NULL) + continue; + + if (plugin->info->type == GAIM_PLUGIN_LOADER) + { + /* We'll just load this right now. */ + if (!gaim_plugin_load(plugin)) + { + gaim_plugin_destroy(plugin); + + continue; + } + + plugin_loaders = g_list_append(plugin_loaders, plugin); + + for (cur = GAIM_PLUGIN_LOADER_INFO(plugin)->exts; + cur != NULL; + cur = cur->next) + { + gaim_plugins_probe(cur->data); + } + } + else if (plugin->info->type == GAIM_PLUGIN_PROTOCOL) + { + /* We'll just load this right now. */ + if (!gaim_plugin_load(plugin)) + { + gaim_plugin_destroy(plugin); + + continue; + } + + /* Make sure we don't load two PRPLs with the same name? */ + if (gaim_find_prpl(plugin->info->id)) + { + /* Nothing to see here--move along, move along */ + gaim_plugin_destroy(plugin); + + continue; + } + + protocol_plugins = g_list_insert_sorted(protocol_plugins, plugin, + (GCompareFunc)compare_prpl); + } + } + + if (probe_cb != NULL) + probe_cb(probe_cb_data); +#endif /* GAIM_PLUGINS */ +} + +gboolean +gaim_plugin_register(GaimPlugin *plugin) +{ + g_return_val_if_fail(plugin != NULL, FALSE); + + /* If this plugin has been registered already then exit */ + if (g_list_find(plugins, plugin)) + return TRUE; + + /* Ensure the plugin has the requisite information */ + if (plugin->info->type == GAIM_PLUGIN_LOADER) + { + GaimPluginLoaderInfo *loader_info; + + loader_info = GAIM_PLUGIN_LOADER_INFO(plugin); + + if (loader_info == NULL) + { + gaim_debug_error("plugins", "%s is unloadable\n", + plugin->path); + return FALSE; + } + } + else if (plugin->info->type == GAIM_PLUGIN_PROTOCOL) + { + GaimPluginProtocolInfo *prpl_info; + + prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(plugin); + + if (prpl_info == NULL) + { + gaim_debug_error("plugins", "%s is unloadable\n", + plugin->path); + return FALSE; + } + } + +#ifdef GAIM_PLUGINS + /* This plugin should be probed and maybe loaded--add it to the queue */ + load_queue = g_list_append(load_queue, plugin); +#else + if (plugin->info != NULL) + { + if (plugin->info->type == GAIM_PLUGIN_PROTOCOL) + protocol_plugins = g_list_insert_sorted(protocol_plugins, plugin, + (GCompareFunc)compare_prpl); + if (plugin->info->load != NULL) + if (!plugin->info->load(plugin)) + return FALSE; + } +#endif + + plugins = g_list_append(plugins, plugin); + + return TRUE; +} + +gboolean +gaim_plugins_enabled(void) +{ +#ifdef GAIM_PLUGINS + return TRUE; +#else + return FALSE; +#endif +} + +void +gaim_plugins_register_probe_notify_cb(void (*func)(void *), void *data) +{ + /* TODO */ + probe_cb = func; + probe_cb_data = data; +} + +void +gaim_plugins_unregister_probe_notify_cb(void (*func)(void *)) +{ + /* TODO */ + probe_cb = NULL; + probe_cb_data = NULL; +} + +void +gaim_plugins_register_load_notify_cb(void (*func)(GaimPlugin *, void *), + void *data) +{ + /* TODO */ + load_cb = func; + load_cb_data = data; +} + +void +gaim_plugins_unregister_load_notify_cb(void (*func)(GaimPlugin *, void *)) +{ + /* TODO */ + load_cb = NULL; + load_cb_data = NULL; +} + +void +gaim_plugins_register_unload_notify_cb(void (*func)(GaimPlugin *, void *), + void *data) +{ + /* TODO */ + unload_cb = func; + unload_cb_data = data; +} + +void +gaim_plugins_unregister_unload_notify_cb(void (*func)(GaimPlugin *, void *)) +{ + /* TODO */ + unload_cb = NULL; + unload_cb_data = NULL; +} + +GaimPlugin * +gaim_plugins_find_with_name(const char *name) +{ + GaimPlugin *plugin; + GList *l; + + for (l = plugins; l != NULL; l = l->next) { + plugin = l->data; + + if (!strcmp(plugin->info->name, name)) + return plugin; + } + + return NULL; +} + +GaimPlugin * +gaim_plugins_find_with_filename(const char *filename) +{ + GaimPlugin *plugin; + GList *l; + + for (l = plugins; l != NULL; l = l->next) { + plugin = l->data; + + if (plugin->path != NULL && !strcmp(plugin->path, filename)) + return plugin; + } + + return NULL; +} + +GaimPlugin * +gaim_plugins_find_with_basename(const char *basename) +{ +#ifdef GAIM_PLUGINS + GaimPlugin *plugin; + GList *l; + char *tmp; + + g_return_val_if_fail(basename != NULL, NULL); + + for (l = plugins; l != NULL; l = l->next) + { + plugin = (GaimPlugin *)l->data; + + if (plugin->path != NULL) { + tmp = gaim_plugin_get_basename(plugin->path); + if (!strcmp(tmp, basename)) + { + g_free(tmp); + return plugin; + } + g_free(tmp); + } + } + +#endif /* GAIM_PLUGINS */ + + return NULL; +} + +GaimPlugin * +gaim_plugins_find_with_id(const char *id) +{ + GaimPlugin *plugin; + GList *l; + + g_return_val_if_fail(id != NULL, NULL); + + for (l = plugins; l != NULL; l = l->next) + { + plugin = l->data; + + if (plugin->info->id != NULL && !strcmp(plugin->info->id, id)) + return plugin; + } + + return NULL; +} + +GList * +gaim_plugins_get_loaded(void) +{ + return loaded_plugins; +} + +GList * +gaim_plugins_get_protocols(void) +{ + return protocol_plugins; +} + +GList * +gaim_plugins_get_all(void) +{ + return plugins; +} + + +GaimPluginAction * +gaim_plugin_action_new(const char* label, void (*callback)(GaimPluginAction *)) +{ + GaimPluginAction *act = g_new0(GaimPluginAction, 1); + + act->label = g_strdup(label); + act->callback = callback; + + return act; +} + +void +gaim_plugin_action_free(GaimPluginAction *action) +{ + g_return_if_fail(action != NULL); + + g_free(action->label); + g_free(action); +} diff -r d10dda2777a9 -r b63ebf84c42b core/plugin.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugin.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,648 @@ +/** + * @file plugin.h Plugin API + * @ingroup core + * + * 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 + */ +#ifndef _GAIM_PLUGIN_H_ +#define _GAIM_PLUGIN_H_ + +#include +#include +#include "signals.h" +#include "value.h" + +typedef struct _GaimPlugin GaimPlugin; +typedef struct _GaimPluginInfo GaimPluginInfo; +typedef struct _GaimPluginUiInfo GaimPluginUiInfo; +typedef struct _GaimPluginLoaderInfo GaimPluginLoaderInfo; + +typedef struct _GaimPluginAction GaimPluginAction; + +typedef int GaimPluginPriority; /**< Plugin priority. */ + +#include "pluginpref.h" + +/** + * Plugin types. + */ +typedef enum +{ + GAIM_PLUGIN_UNKNOWN = -1, /**< Unknown type. */ + GAIM_PLUGIN_STANDARD = 0, /**< Standard plugin. */ + GAIM_PLUGIN_LOADER, /**< Loader plugin. */ + GAIM_PLUGIN_PROTOCOL /**< Protocol plugin. */ + +} GaimPluginType; + +#define GAIM_PRIORITY_DEFAULT 0 +#define GAIM_PRIORITY_HIGHEST 9999 +#define GAIM_PRIORITY_LOWEST -9999 + +#define GAIM_PLUGIN_FLAG_INVISIBLE 0x01 + +#define GAIM_PLUGIN_MAGIC 5 /* once we hit 6.0.0 I think we can remove this */ + +/** + * Detailed information about a plugin. + * + * This is used in the version 2.0 API and up. + */ +/* TODO We need to figure out exactly what parts of this are required. The + * dependent plugin unloading stuff was causing crashes with perl and tcl + * plugins because they didn't set ids and the dependency code was requiring + * them. Then we need to actually make sure that plugins have all the right + * parts before loading them. */ +struct _GaimPluginInfo +{ + unsigned int magic; + unsigned int major_version; + unsigned int minor_version; + GaimPluginType type; + char *ui_requirement; + unsigned long flags; + GList *dependencies; + GaimPluginPriority priority; + + char *id; + char *name; + char *version; + char *summary; + char *description; + char *author; + char *homepage; + + /** + * If a plugin defines a 'load' function, and it returns FALSE, + * then the plugin will not be loaded. + */ + gboolean (*load)(GaimPlugin *plugin); + gboolean (*unload)(GaimPlugin *plugin); + void (*destroy)(GaimPlugin *plugin); + + void *ui_info; /**< Used only by UI-specific plugins to build a preference screen with a custom UI */ + void *extra_info; + GaimPluginUiInfo *prefs_info; /**< Used by any plugin to display preferences. If #ui_info has been specified, this will be ignored. */ + GList *(*actions)(GaimPlugin *plugin, gpointer context); +}; + +/** + * Extra information for loader plugins. + */ +struct _GaimPluginLoaderInfo +{ + GList *exts; + + gboolean (*probe)(GaimPlugin *plugin); + gboolean (*load)(GaimPlugin *plugin); + gboolean (*unload)(GaimPlugin *plugin); + void (*destroy)(GaimPlugin *plugin); +}; + +/** + * A plugin handle. + */ +struct _GaimPlugin +{ + gboolean native_plugin; /**< Native C plugin. */ + gboolean loaded; /**< The loaded state. */ + void *handle; /**< The module handle. */ + char *path; /**< The path to the plugin. */ + GaimPluginInfo *info; /**< The plugin information. */ + char *error; + void *ipc_data; /**< IPC data. */ + void *extra; /**< Plugin-specific data. */ + gboolean unloadable; /**< Unloadable */ + GList *dependent_plugins; /**< Plugins depending on this */ +}; + +#define GAIM_PLUGIN_LOADER_INFO(plugin) \ + ((GaimPluginLoaderInfo *)(plugin)->info->extra_info) + +struct _GaimPluginUiInfo { + GaimPluginPrefFrame *(*get_plugin_pref_frame)(GaimPlugin *plugin); + + int page_num; /**< Reserved */ + GaimPluginPrefFrame *frame; /**< Reserved */ +}; + +#define GAIM_PLUGIN_HAS_PREF_FRAME(plugin) \ + ((plugin)->info != NULL && (plugin)->info->prefs_info != NULL) + +#define GAIM_PLUGIN_UI_INFO(plugin) \ + ((GaimPluginUiInfo*)(plugin)->info->prefs_info) + + +/** + * The structure used in the actions member of GaimPluginInfo + */ +struct _GaimPluginAction { + char *label; + void (*callback)(GaimPluginAction *); + + /** set to the owning plugin */ + GaimPlugin *plugin; + + /** NULL for plugin actions menu, set to the GaimConnection for + account actions menu */ + gpointer context; +}; + +#define GAIM_PLUGIN_HAS_ACTIONS(plugin) \ + ((plugin)->info != NULL && (plugin)->info->actions != NULL) + +#define GAIM_PLUGIN_ACTIONS(plugin, context) \ + (GAIM_PLUGIN_HAS_ACTIONS(plugin)? \ + (plugin)->info->actions(plugin, context): NULL) + + +/** + * Handles the initialization of modules. + */ +#if !defined(GAIM_PLUGINS) || defined(GAIM_STATIC_PRPL) +# define GAIM_INIT_PLUGIN(pluginname, initfunc, plugininfo) \ + gboolean gaim_init_##pluginname##_plugin(void);\ + gboolean gaim_init_##pluginname##_plugin(void) { \ + GaimPlugin *plugin = gaim_plugin_new(TRUE, NULL); \ + plugin->info = &(plugininfo); \ + initfunc((plugin)); \ + gaim_plugin_load((plugin)); \ + return gaim_plugin_register(plugin); \ + } +#else /* GAIM_PLUGINS && !GAIM_STATIC_PRPL */ +# define GAIM_INIT_PLUGIN(pluginname, initfunc, plugininfo) \ + G_MODULE_EXPORT gboolean gaim_init_plugin(GaimPlugin *plugin); \ + G_MODULE_EXPORT gboolean gaim_init_plugin(GaimPlugin *plugin) { \ + plugin->info = &(plugininfo); \ + initfunc((plugin)); \ + return gaim_plugin_register(plugin); \ + } +#endif + + +#ifdef __cplusplus +extern "C" { +#endif + +/**************************************************************************/ +/** @name Plugin API */ +/**************************************************************************/ +/*@{*/ + +/** + * Creates a new plugin structure. + * + * @param native Whether or not the plugin is native. + * @param path The path to the plugin, or @c NULL if statically compiled. + * + * @return A new GaimPlugin structure. + */ +GaimPlugin *gaim_plugin_new(gboolean native, const char *path); + +/** + * Probes a plugin, retrieving the information on it and adding it to the + * list of available plugins. + * + * @param filename The plugin's filename. + * + * @return The plugin handle. + * + * @see gaim_plugin_load() + * @see gaim_plugin_destroy() + */ +GaimPlugin *gaim_plugin_probe(const char *filename); + +/** + * Registers a plugin and prepares it for loading. + * + * This shouldn't be called by anything but the internal module code. + * Plugins should use the GAIM_INIT_PLUGIN() macro to register themselves + * with the core. + * + * @param plugin The plugin to register. + * + * @return @c TRUE if the plugin was registered successfully. Otherwise + * @c FALSE is returned (this happens if the plugin does not contain + * the necessary information). + */ +gboolean gaim_plugin_register(GaimPlugin *plugin); + +/** + * Attempts to load a previously probed plugin. + * + * @param plugin The plugin to load. + * + * @return @c TRUE if successful, or @c FALSE otherwise. + * + * @see gaim_plugin_reload() + * @see gaim_plugin_unload() + */ +gboolean gaim_plugin_load(GaimPlugin *plugin); + +/** + * Unloads the specified plugin. + * + * @param plugin The plugin handle. + * + * @return @c TRUE if successful, or @c FALSE otherwise. + * + * @see gaim_plugin_load() + * @see gaim_plugin_reload() + */ +gboolean gaim_plugin_unload(GaimPlugin *plugin); + +/** + * Reloads a plugin. + * + * @param plugin The old plugin handle. + * + * @return @c TRUE if successful, or @c FALSE otherwise. + * + * @see gaim_plugin_load() + * @see gaim_plugin_unload() + */ +gboolean gaim_plugin_reload(GaimPlugin *plugin); + +/** + * Unloads a plugin and destroys the structure from memory. + * + * @param plugin The plugin handle. + */ +void gaim_plugin_destroy(GaimPlugin *plugin); + +/** + * Returns whether or not a plugin is currently loaded. + * + * @param plugin The plugin. + * + * @return @c TRUE if loaded, or @c FALSE otherwise. + */ +gboolean gaim_plugin_is_loaded(const GaimPlugin *plugin); + +/** + * Returns whether or not a plugin is unloadable. + * + * If this returns @c TRUE, the plugin is guaranteed to not + * be loadable. However, a return value of @c FALSE does not + * guarantee the plugin is loadable. + * + * @param plugin The plugin. + * + * @return @c TRUE if the plugin is known to be unloadable,\ + * @c FALSE otherwise + */ +gboolean gaim_plugin_is_unloadable(const GaimPlugin *plugin); + +/** + * Returns a plugin's id. + * + * @param plugin The plugin. + * + * @return The plugin's id. + */ +const gchar *gaim_plugin_get_id(const GaimPlugin *plugin); + +/** + * Returns a plugin's name. + * + * @param plugin The plugin. + * + * @return THe name of the plugin, or @c NULL. + */ +const gchar *gaim_plugin_get_name(const GaimPlugin *plugin); + +/** + * Returns a plugin's version. + * + * @param plugin The plugin. + * + * @return The plugin's version or @c NULL. + */ +const gchar *gaim_plugin_get_version(const GaimPlugin *plugin); + +/** + * Returns a plugin's summary. + * + * @param plugin The plugin. + * + * @return The plugin's summary. + */ +const gchar *gaim_plugin_get_summary(const GaimPlugin *plugin); + +/** + * Returns a plugin's description. + * + * @param plugin The plugin. + * + * @return The plugin's description. + */ +const gchar *gaim_plugin_get_description(const GaimPlugin *plugin); + +/** + * Returns a plugin's author. + * + * @param plugin The plugin. + * + * @return The plugin's author. + */ +const gchar *gaim_plugin_get_author(const GaimPlugin *plugin); + +/** + * Returns a plugin's homepage. + * + * @param plugin The plugin. + * + * @return The plugin's homepage. + */ +const gchar *gaim_plugin_get_homepage(const GaimPlugin *plugin); + +/*@}*/ + +/**************************************************************************/ +/** @name Plugin IPC API */ +/**************************************************************************/ +/*@{*/ + +/** + * Registers an IPC command in a plugin. + * + * @param plugin The plugin to register the command with. + * @param command The name of the command. + * @param func The function to execute. + * @param marshal The marshalling function. + * @param ret_value The return value type. + * @param num_params The number of parameters. + * @param ... The parameter types. + * + * @return TRUE if the function was registered successfully, or + * FALSE otherwise. + */ +gboolean gaim_plugin_ipc_register(GaimPlugin *plugin, const char *command, + GaimCallback func, + GaimSignalMarshalFunc marshal, + GaimValue *ret_value, int num_params, ...); + +/** + * Unregisters an IPC command in a plugin. + * + * @param plugin The plugin to unregister the command from. + * @param command The name of the command. + */ +void gaim_plugin_ipc_unregister(GaimPlugin *plugin, const char *command); + +/** + * Unregisters all IPC commands in a plugin. + * + * @param plugin The plugin to unregister the commands from. + */ +void gaim_plugin_ipc_unregister_all(GaimPlugin *plugin); + +/** + * Returns a list of value types used for an IPC command. + * + * @param plugin The plugin. + * @param command The name of the command. + * @param ret_value The returned return value. + * @param num_params The returned number of parameters. + * @param params The returned list of parameters. + * + * @return TRUE if the command was found, or FALSE otherwise. + */ +gboolean gaim_plugin_ipc_get_params(GaimPlugin *plugin, const char *command, + GaimValue **ret_value, int *num_params, + GaimValue ***params); + +/** + * Executes an IPC command. + * + * @param plugin The plugin to execute the command on. + * @param command The name of the command. + * @param ok TRUE if the call was successful, or FALSE otherwise. + * @param ... The parameters to pass. + * + * @return The return value, which will be NULL if the command doesn't + * return a value. + */ +void *gaim_plugin_ipc_call(GaimPlugin *plugin, const char *command, + gboolean *ok, ...); + +/*@}*/ + +/**************************************************************************/ +/** @name Plugins API */ +/**************************************************************************/ +/*@{*/ + +/** + * Add a new directory to search for plugins + * + * @param path The new search path. + */ +void gaim_plugins_add_search_path(const char *path); + +/** + * Unloads all loaded plugins. + */ +void gaim_plugins_unload_all(void); + +/** + * Destroys all registered plugins. + */ +void gaim_plugins_destroy_all(void); + +/** + * Attempts to load all the plugins in the specified preference key + * that were loaded when gaim last quit. + * + * @param key The preference key containing the list of plugins. + */ +void gaim_plugins_load_saved(const char *key); + +/** + * Probes for plugins in the registered module paths. + * + * @param ext The extension type to probe for, or @c NULL for all. + * + * @see gaim_plugin_set_probe_path() + */ +void gaim_plugins_probe(const char *ext); + +/** + * Returns whether or not plugin support is enabled. + * + * @return TRUE if plugin support is enabled, or FALSE otherwise. + */ +gboolean gaim_plugins_enabled(void); + +/** + * Registers a function that will be called when probing is finished. + * + * @param func The callback function. + * @param data Data to pass to the callback. + */ +void gaim_plugins_register_probe_notify_cb(void (*func)(void *), void *data); + +/** + * Unregisters a function that would be called when probing is finished. + * + * @param func The callback function. + */ +void gaim_plugins_unregister_probe_notify_cb(void (*func)(void *)); + +/** + * Registers a function that will be called when a plugin is loaded. + * + * @param func The callback function. + * @param data Data to pass to the callback. + */ +void gaim_plugins_register_load_notify_cb(void (*func)(GaimPlugin *, void *), + void *data); + +/** + * Unregisters a function that would be called when a plugin is loaded. + * + * @param func The callback function. + */ +void gaim_plugins_unregister_load_notify_cb(void (*func)(GaimPlugin *, void *)); + +/** + * Registers a function that will be called when a plugin is unloaded. + * + * @param func The callback function. + * @param data Data to pass to the callback. + */ +void gaim_plugins_register_unload_notify_cb(void (*func)(GaimPlugin *, void *), + void *data); + +/** + * Unregisters a function that would be called when a plugin is unloaded. + * + * @param func The callback function. + */ +void gaim_plugins_unregister_unload_notify_cb(void (*func)(GaimPlugin *, + void *)); + +/** + * Finds a plugin with the specified name. + * + * @param name The plugin name. + * + * @return The plugin if found, or @c NULL if not found. + */ +GaimPlugin *gaim_plugins_find_with_name(const char *name); + +/** + * Finds a plugin with the specified filename (filename with a path). + * + * @param filename The plugin filename. + * + * @return The plugin if found, or @c NULL if not found. + */ +GaimPlugin *gaim_plugins_find_with_filename(const char *filename); + +/** + * Finds a plugin with the specified basename (filename without a path). + * + * @param basename The plugin basename. + * + * @return The plugin if found, or @c NULL if not found. + */ +GaimPlugin *gaim_plugins_find_with_basename(const char *basename); + +/** + * Finds a plugin with the specified plugin ID. + * + * @param id The plugin ID. + * + * @return The plugin if found, or @c NULL if not found. + */ +GaimPlugin *gaim_plugins_find_with_id(const char *id); + +/** + * Returns a list of all loaded plugins. + * + * @return A list of all loaded plugins. + */ +GList *gaim_plugins_get_loaded(void); + +/** + * Returns a list of all valid protocol plugins. A protocol + * plugin is considered invalid if it does not contain the call + * to the GAIM_INIT_PLUGIN() macro, or if it was compiled + * against an incompatable API version. + * + * @return A list of all protocol plugins. + */ +GList *gaim_plugins_get_protocols(void); + +/** + * Returns a list of all plugins, whether loaded or not. + * + * @return A list of all plugins. + */ +GList *gaim_plugins_get_all(void); + +/*@}*/ + +/**************************************************************************/ +/** @name Plugins SubSytem API */ +/**************************************************************************/ +/*@{*/ + +/** + * Returns the plugin subsystem handle. + * + * @return The plugin sybsystem handle. + */ +void *gaim_plugins_get_handle(void); + +/** + * Initializes the plugin subsystem + */ +void gaim_plugins_init(void); + +/** + * Uninitializes the plugin subsystem + */ +void gaim_plugins_uninit(void); + +/*@}*/ + +/** + * Allocates and returns a new GaimPluginAction. + * + * @param label The description of the action to show to the user. + * @param callback The callback to call when the user selects this action. + */ +GaimPluginAction *gaim_plugin_action_new(const char* label, void (*callback)(GaimPluginAction *)); + +/** + * Frees a GaimPluginAction + * + * @param action The GaimPluginAction to free. + */ +void gaim_plugin_action_free(GaimPluginAction *action); + +#ifdef __cplusplus +} +#endif + +#endif /* _GAIM_PLUGIN_H_ */ diff -r d10dda2777a9 -r b63ebf84c42b core/pluginpref.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/pluginpref.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,319 @@ +/** + * 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 + */ +#ifdef HAVE_CONFIG_H +# include +#endif + +#include + +#include "debug.h" +#include "internal.h" +#include "pluginpref.h" +#include "prefs.h" + +struct _GaimPluginPrefFrame +{ + GList *prefs; +}; + +struct _GaimPluginPref +{ + char *name; + char *label; + + GaimPluginPrefType type; + + int min; + int max; + GList *choices; + unsigned int max_length; + gboolean masked; + GaimStringFormatType format; +}; + +GaimPluginPrefFrame * +gaim_plugin_pref_frame_new() +{ + GaimPluginPrefFrame *frame; + + frame = g_new0(GaimPluginPrefFrame, 1); + + return frame; +} + +void +gaim_plugin_pref_frame_destroy(GaimPluginPrefFrame *frame) +{ + g_return_if_fail(frame != NULL); + + g_list_foreach(frame->prefs, (GFunc)gaim_plugin_pref_destroy, NULL); + g_list_free(frame->prefs); + g_free(frame); +} + +void +gaim_plugin_pref_frame_add(GaimPluginPrefFrame *frame, GaimPluginPref *pref) +{ + g_return_if_fail(frame != NULL); + g_return_if_fail(pref != NULL); + + frame->prefs = g_list_append(frame->prefs, pref); +} + +GList * +gaim_plugin_pref_frame_get_prefs(GaimPluginPrefFrame *frame) +{ + g_return_val_if_fail(frame != NULL, NULL); + g_return_val_if_fail(frame->prefs != NULL, NULL); + + return frame->prefs; +} + +GaimPluginPref * +gaim_plugin_pref_new() +{ + GaimPluginPref *pref; + + pref = g_new0(GaimPluginPref, 1); + + return pref; +} + +GaimPluginPref * +gaim_plugin_pref_new_with_name(const char *name) +{ + GaimPluginPref *pref; + + g_return_val_if_fail(name != NULL, NULL); + + pref = g_new0(GaimPluginPref, 1); + pref->name = g_strdup(name); + + return pref; +} + +GaimPluginPref * +gaim_plugin_pref_new_with_label(const char *label) +{ + GaimPluginPref *pref; + + g_return_val_if_fail(label != NULL, NULL); + + pref = g_new0(GaimPluginPref, 1); + pref->label = g_strdup(label); + + return pref; +} + +GaimPluginPref * +gaim_plugin_pref_new_with_name_and_label(const char *name, const char *label) +{ + GaimPluginPref *pref; + + g_return_val_if_fail(name != NULL, NULL); + g_return_val_if_fail(label != NULL, NULL); + + pref = g_new0(GaimPluginPref, 1); + pref->name = g_strdup(name); + pref->label = g_strdup(label); + + return pref; +} + +void +gaim_plugin_pref_destroy(GaimPluginPref *pref) +{ + g_return_if_fail(pref != NULL); + + g_free(pref->name); + g_free(pref->label); + g_list_free(pref->choices); + g_free(pref); +} + +void +gaim_plugin_pref_set_name(GaimPluginPref *pref, const char *name) +{ + g_return_if_fail(pref != NULL); + g_return_if_fail(name != NULL); + + g_free(pref->name); + pref->name = g_strdup(name); +} + +const char * +gaim_plugin_pref_get_name(GaimPluginPref *pref) +{ + g_return_val_if_fail(pref != NULL, NULL); + + return pref->name; +} + +void +gaim_plugin_pref_set_label(GaimPluginPref *pref, const char *label) +{ + g_return_if_fail(pref != NULL); + g_return_if_fail(label != NULL); + + g_free(pref->label); + pref->label = g_strdup(label); +} + +const char * +gaim_plugin_pref_get_label(GaimPluginPref *pref) +{ + g_return_val_if_fail(pref != NULL, NULL); + + return pref->label; +} + +void +gaim_plugin_pref_set_bounds(GaimPluginPref *pref, int min, int max) +{ + int tmp; + + g_return_if_fail(pref != NULL); + g_return_if_fail(pref->name != NULL); + + if (gaim_prefs_get_type(pref->name) != GAIM_PREF_INT) + { + gaim_debug_info("pluginpref", + "gaim_plugin_pref_set_bounds: %s is not an integer pref\n", + pref->name); + return; + } + + if (min > max) + { + tmp = min; + min = max; + max = tmp; + } + + pref->min = min; + pref->max = max; +} + +void gaim_plugin_pref_get_bounds(GaimPluginPref *pref, int *min, int *max) +{ + g_return_if_fail(pref != NULL); + g_return_if_fail(pref->name != NULL); + + if (gaim_prefs_get_type(pref->name) != GAIM_PREF_INT) + { + gaim_debug(GAIM_DEBUG_INFO, "pluginpref", + "gaim_plugin_pref_get_bounds: %s is not an integer pref\n", + pref->name); + return; + } + + *min = pref->min; + *max = pref->max; +} + +void +gaim_plugin_pref_set_type(GaimPluginPref *pref, GaimPluginPrefType type) +{ + g_return_if_fail(pref != NULL); + + pref->type = type; +} + +GaimPluginPrefType +gaim_plugin_pref_get_type(GaimPluginPref *pref) +{ + g_return_val_if_fail(pref != NULL, GAIM_PLUGIN_PREF_NONE); + + return pref->type; +} + +void +gaim_plugin_pref_add_choice(GaimPluginPref *pref, const char *label, gpointer choice) +{ + g_return_if_fail(pref != NULL); + g_return_if_fail(label != NULL); + g_return_if_fail(choice || gaim_prefs_get_type(pref->name) == GAIM_PREF_INT); + + pref->choices = g_list_append(pref->choices, (gpointer)label); + pref->choices = g_list_append(pref->choices, choice); +} + +GList * +gaim_plugin_pref_get_choices(GaimPluginPref *pref) +{ + g_return_val_if_fail(pref != NULL, NULL); + + return pref->choices; +} + +void +gaim_plugin_pref_set_max_length(GaimPluginPref *pref, unsigned int max_length) +{ + g_return_if_fail(pref != NULL); + + pref->max_length = max_length; +} + +unsigned int +gaim_plugin_pref_get_max_length(GaimPluginPref *pref) +{ + g_return_val_if_fail(pref != NULL, 0); + + return pref->max_length; +} + +void +gaim_plugin_pref_set_masked(GaimPluginPref *pref, gboolean masked) +{ + g_return_if_fail(pref != NULL); + + pref->masked = masked; +} + +gboolean +gaim_plugin_pref_get_masked(GaimPluginPref *pref) +{ + g_return_val_if_fail(pref != NULL, FALSE); + + return pref->masked; +} + +void +gaim_plugin_pref_set_format_type(GaimPluginPref *pref, GaimStringFormatType format) +{ + g_return_if_fail(pref != NULL); + g_return_if_fail(pref->type == GAIM_PLUGIN_PREF_STRING_FORMAT); + + pref->format = format; +} + +GaimStringFormatType +gaim_plugin_pref_get_format_type(GaimPluginPref *pref) +{ + g_return_val_if_fail(pref != NULL, 0); + + if (pref->type != GAIM_PLUGIN_PREF_STRING_FORMAT) + return GAIM_STRING_FORMAT_TYPE_NONE; + + return pref->format; +} + diff -r d10dda2777a9 -r b63ebf84c42b core/pluginpref.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/pluginpref.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,258 @@ +/** + * @file pluginpref.h Plugin Preferences API + * @ingroup core + * + * 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 + * + */ +#ifndef _GAIM_PLUGINPREF_H_ +#define _GAIM_PLUGINPREF_H_ + +typedef struct _GaimPluginPrefFrame GaimPluginPrefFrame; +typedef struct _GaimPluginPref GaimPluginPref; + +typedef enum { + GAIM_PLUGIN_PREF_NONE, + GAIM_PLUGIN_PREF_CHOICE, + GAIM_PLUGIN_PREF_INFO, /**< no-value label */ + GAIM_PLUGIN_PREF_STRING_FORMAT +} GaimPluginPrefType; + +#include +#include "prefs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/**************************************************************************/ +/** @name Plugin Preference API */ +/**************************************************************************/ +/*@{*/ + +/** + * Create a new plugin preference frame + * + * @return a new GaimPluginPrefFrame + */ +GaimPluginPrefFrame *gaim_plugin_pref_frame_new(void); + +/** + * Destroy a plugin preference frame + * + * @param frame The plugin frame to destroy + */ +void gaim_plugin_pref_frame_destroy(GaimPluginPrefFrame *frame); + +/** + * Adds a plugin preference to a plugin preference frame + * + * @param frame The plugin frame to add the preference to + * @param pref The preference to add to the frame + */ +void gaim_plugin_pref_frame_add(GaimPluginPrefFrame *frame, GaimPluginPref *pref); + +/** + * Get the plugin preferences from a plugin preference frame + * + * @param frame The plugin frame to get the plugin preferences from + * @return a GList of plugin preferences + */ +GList *gaim_plugin_pref_frame_get_prefs(GaimPluginPrefFrame *frame); + +/** + * Create a new plugin preference + * + * @return a new GaimPluginPref + */ +GaimPluginPref *gaim_plugin_pref_new(void); + +/** + * Create a new plugin preference with name + * + * @param name The name of the pref + * @return a new GaimPluginPref + */ +GaimPluginPref *gaim_plugin_pref_new_with_name(const char *name); + +/** + * Create a new plugin preference with label + * + * @param label The label to be displayed + * @return a new GaimPluginPref + */ +GaimPluginPref *gaim_plugin_pref_new_with_label(const char *label); + +/** + * Create a new plugin preference with name and label + * + * @param name The name of the pref + * @param label The label to be displayed + * @return a new GaimPluginPref + */ +GaimPluginPref *gaim_plugin_pref_new_with_name_and_label(const char *name, const char *label); + +/** + * Destroy a plugin preference + * + * @param pref The preference to destroy + */ +void gaim_plugin_pref_destroy(GaimPluginPref *pref); + +/** + * Set a plugin pref name + * + * @param pref The plugin pref + * @param name The name of the pref + */ +void gaim_plugin_pref_set_name(GaimPluginPref *pref, const char *name); + +/** + * Get a plugin pref name + * + * @param pref The plugin pref + * @return The name of the pref + */ +const char *gaim_plugin_pref_get_name(GaimPluginPref *pref); + +/** + * Set a plugin pref label + * + * @param pref The plugin pref + * @param label The label for the plugin pref + */ +void gaim_plugin_pref_set_label(GaimPluginPref *pref, const char *label); + +/** + * Get a plugin pref label + * + * @param pref The plugin pref + * @return The label for the plugin pref + */ +const char *gaim_plugin_pref_get_label(GaimPluginPref *pref); + +/** + * Set the bounds for an integer pref + * + * @param pref The plugin pref + * @param min The min value + * @param max The max value + */ +void gaim_plugin_pref_set_bounds(GaimPluginPref *pref, int min, int max); + +/** + * Get the bounds for an integer pref + * + * @param pref The plugin pref + * @param min The min value + * @param max The max value + */ +void gaim_plugin_pref_get_bounds(GaimPluginPref *pref, int *min, int *max); + +/** + * Set the type of a plugin pref + * + * @param pref The plugin pref + * @param type The type + */ +void gaim_plugin_pref_set_type(GaimPluginPref *pref, GaimPluginPrefType type); + +/** + * Get the type of a plugin pref + * + * @param pref The plugin pref + * @return The type + */ +GaimPluginPrefType gaim_plugin_pref_get_type(GaimPluginPref *pref); + +/** + * Set the choices for a choices plugin pref + * + * @param pref The plugin pref + * @param label The label for the choice + * @param choice A gpointer of the choice + */ +void gaim_plugin_pref_add_choice(GaimPluginPref *pref, const char *label, gpointer choice); + +/** + * Get the choices for a choices plugin pref + * + * @param pref The plugin pref + * @return GList of the choices + */ +GList *gaim_plugin_pref_get_choices(GaimPluginPref *pref); + +/** + * Set the max length for a string plugin pref + * + * @param pref The plugin pref + * @param max_length The max length of the string + */ +void gaim_plugin_pref_set_max_length(GaimPluginPref *pref, unsigned int max_length); + +/** + * Get the max length for a string plugin pref + * + * @param pref The plugin pref + * @return the max length + */ +unsigned int gaim_plugin_pref_get_max_length(GaimPluginPref *pref); + +/** + * Sets the masking of a string plugin pref + * + * @param pref The plugin pref + * @param mask The value to set + */ +void gaim_plugin_pref_set_masked(GaimPluginPref *pref, gboolean mask); + +/** + * Gets the masking of a string plugin pref + * + * @param pref The plugin pref + * @return The masking + */ +gboolean gaim_plugin_pref_get_masked(GaimPluginPref *pref); + +/** + * Sets the format type for a formattable-string plugin pref. You need to set the + * pref type to GAIM_PLUGIN_PREF_STRING_FORMAT first before setting the format. + * + * @param pref The plugin pref + * @param format The format of the string + */ +void gaim_plugin_pref_set_format_type(GaimPluginPref *pref, GaimStringFormatType format); + +/** + * Gets the format type of the formattable-string plugin pref. + * + * @param pref The plugin pref + * @return The format of the pref + */ +GaimStringFormatType gaim_plugin_pref_get_format_type(GaimPluginPref *pref); + +/*@}*/ + +#ifdef __cplusplus +} +#endif + +#endif /* _GAIM_PLUGINPREF_H_ */ diff -r d10dda2777a9 -r b63ebf84c42b core/plugins/Makefile.am --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/Makefile.am Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,102 @@ +DIST_SUBDIRS = mono perl ssl tcl + +if USE_PERL +# Fix the perl plugin to not use gtk first +# PERL_DIR = perl +PERL_DIR = +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 + +idle_la_LDFLAGS = -module -avoid-version $(GLIB_LIBS) +psychic_la_LDFLAGS = -module -avoid-version $(GLIB_LIBS) +statenotify_la_LDFLAGS = -module -avoid-version $(GLIB_LIBS) + +# this can't be in a conditional otherwise automake 1.4 yells +dbus_example_la_LDFLAGS = -module -avoid-version $(GLIB_LIBS) $(DBUS_LIBS) + +if PLUGINS + +plugin_LTLIBRARIES = \ + idle.la \ + psychic.la \ + statenotify.la \ + $(DBUS_LTLIB) + + +idle_la_SOURCES = idle.c +psychic_la_SOURCES = psychic.c +statenotify_la_SOURCES = statenotify.c + +if ENABLE_DBUS + +CLEANFILES = dbus-example-bindings.c +dbus_example_la_SOURCES = dbus-example.c + +.PHONY: always + +$(top_builddir)/core/dbus-types.h: always + cd $(@D) && $(MAKE) $(AM_MAKEFLAGS) $(@F) + +dbus-example-bindings.c: $(top_srcdir)/core/dbus-analyze-functions.py $(dbus_example_la_SOURCES) + cat $(srcdir)/$(dbus_example_la_SOURCES) | \ + $(PYTHON) $(top_srcdir)/core/dbus-analyze-functions.py --export-only > $@ + +$(dbus_example_la_OBJECTS) dbus-example.so: dbus-example-bindings.c $(top_builddir)/core/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)/core \ + -I$(top_srcdir)/core \ + $(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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/ciphertest.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/ciphertest.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,287 @@ +/* + * A plugin to test the ciphers that ship with gaim + * + * Copyright (C) 2004, Gary Kramlich + * + * 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 +#endif + +#ifndef GAIM_PLUGINS +#define GAIM_PLUGINS +#endif + +#include "internal.h" + +#include +#include + +#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 ", /**< 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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/codeinline.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/codeinline.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,91 @@ +/* + * 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, "", -1); + m = g_strjoinv("", ms); + g_strfreev(ms); + + ms = g_strsplit(m, "", -1); + g_free(m); + m = g_strjoinv("", 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); + + 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 ", + "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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/dbus-buddyicons-example.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/dbus-buddyicons-example.py Sat Dec 16 04:59:55 2006 +0000 @@ -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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/dbus-example.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/dbus-example.c Sat Dec 16 04:59:55 2006 +0000 @@ -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 +#include +#include + +#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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/filectl.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/filectl.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,270 @@ +/** + * Send commands to Gaim via ~/.gaim/control + * + * Originally by Eric Warmenhoven + * Compile fixes/mini hacks Alex Bennee + * and Brian Tarricone + */ + +/* system includes */ +#include +#include +#include +#include +#include +#include +#include + +#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 ", /**< 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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/fortuneprofile.pl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/fortuneprofile.pl Sat Dec 16 04:59:55 2006 +0000 @@ -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 ", + url => "http://gaim.sf.net/", + + load => "plugin_load" +); + +sub plugin_init { + return %PLUGIN_INFO; +} + +sub plugin_load { + $plugin = shift; + + $tab = " "; + $tab = $tab . $tab . $tab . $tab; + $nl = "
"; + + $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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/gaim.pl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/gaim.pl Sat Dec 16 04:59:55 2006 +0000 @@ -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 <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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/idle.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/idle.c Sat Dec 16 04:59:55 2006 +0000 @@ -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 ", + 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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/ipc-test-client.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/ipc-test-client.c Sat Dec 16 04:59:55 2006 +0000 @@ -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 ", /**< 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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/ipc-test-server.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/ipc-test-server.c Sat Dec 16 04:59:55 2006 +0000 @@ -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 ", /**< 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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/mono/BooPlugin.boo --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/mono/BooPlugin.boo Sat Dec 16 04:59:55 2006 +0000 @@ -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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/mono/GetBuddyBack.cs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/mono/GetBuddyBack.cs Sat Dec 16 04:59:55 2006 +0000 @@ -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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/mono/MPlugin.cs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/mono/MPlugin.cs Sat Dec 16 04:59:55 2006 +0000 @@ -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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/mono/Makefile.am --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/mono/Makefile.am Sat Dec 16 04:59:55 2006 +0000 @@ -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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/mono/api/Buddy.cs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/mono/api/Buddy.cs Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,11 @@ +namespace Gaim +{ + public class Buddy + { + 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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/mono/api/BuddyList.cs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/mono/api/BuddyList.cs Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,20 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Gaim +{ + 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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/mono/api/Debug.cs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/mono/api/Debug.cs Sat Dec 16 04:59:55 2006 +0000 @@ -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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/mono/api/Event.cs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/mono/api/Event.cs Sat Dec 16 04:59:55 2006 +0000 @@ -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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/mono/api/GaimPlugin.cs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/mono/api/GaimPlugin.cs Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,38 @@ +namespace Gaim +{ + public class GaimPluginInfo + { + private string name; + private string version; + private string summary; + private string description; + private string author; + private string homepage; + + public GaimPluginInfo(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 GaimPlugin + { + public abstract void Load(); + public abstract void Unload(); + public abstract void Destroy(); + + public abstract GaimPluginInfo Info(); + } +} diff -r d10dda2777a9 -r b63ebf84c42b core/plugins/mono/api/Makefile.am --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/mono/api/Makefile.am Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,23 @@ +monodir=$(libdir)/gaim +mono_sources = \ + GaimPlugin.cs \ + Debug.cs \ + BuddyList.cs \ + Buddy.cs \ + Signal.cs \ + Event.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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/mono/api/Signal.cs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/mono/api/Signal.cs Sat Dec 16 04:59:55 2006 +0000 @@ -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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/mono/api/Status.cs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/mono/api/Status.cs Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,9 @@ +namespace Gaim +{ + public class Status + { + private string id; + + public string Id { get { return id; } set { id = value; } } + } +} diff -r d10dda2777a9 -r b63ebf84c42b core/plugins/mono/loader/Makefile.am --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/mono/loader/Makefile.am Sat Dec 16 04:59:55 2006 +0000 @@ -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)/core \ + $(DEBUG_CFLAGS) \ + $(PLUGIN_CFLAGS) \ + $(MONO_CFLAGS) diff -r d10dda2777a9 -r b63ebf84c42b core/plugins/mono/loader/blist-glue.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/mono/loader/blist-glue.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,26 @@ +#include +#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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/mono/loader/debug-glue.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/mono/loader/debug-glue.c Sat Dec 16 04:59:55 2006 +0000 @@ -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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/mono/loader/mono-glue.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/mono/loader/mono-glue.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,19 @@ +#ifndef _GAIM_MONO_LOADER_GLUE_H_ +#define _GAIM_MONO_LOADER_GLUE_H_ + +#include +#include +#include +#include + +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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/mono/loader/mono-helper.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/mono/loader/mono-helper.c Sat Dec 16 04:59:55 2006 +0000 @@ -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 +#endif + +#include +#include +#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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/mono/loader/mono-helper.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/mono/loader/mono-helper.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,73 @@ +#ifndef _GAIM_MONO_LOADER_MONO_HELPER_H_ +#define _GAIM_MONO_LOADER_MONO_HELPER_H_ + +#include +#include +#include +#include +#include +#include +#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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/mono/loader/mono.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/mono/loader/mono.c Sat Dec 16 04:59:55 2006 +0000 @@ -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 +#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 ", + 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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/mono/loader/signal-glue.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/mono/loader/signal-glue.c Sat Dec 16 04:59:55 2006 +0000 @@ -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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/mono/loader/status-glue.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/mono/loader/status-glue.c Sat Dec 16 04:59:55 2006 +0000 @@ -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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/Makefile.am --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/Makefile.am Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,165 @@ +plugindir = $(libdir)/gaim + +perl_dirs = common + +plugin_LTLIBRARIES = perl.la libgaimperl.la + +perl_la_LDFLAGS = -module -avoid-version $(GLIB_LIBS) $(PERL_LIBS) +perl_la_LIBADD = $(PERL_LIBS) -L. -L.libs -lgaimperl +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 $(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/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)/core \ + -I$(top_srcdir)/gtk \ # FIXME + $(DEBUG_CFLAGS) \ + $(GTK_CFLAGS) \ + $(PLUGIN_CFLAGS) \ + $(PERL_CFLAGS) diff -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/Makefile.mingw --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/Makefile.mingw Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,137 @@ +# +# Makefile.mingw +# +# Description: Makefile for perl plugin loader plugin. +# + +# +# PATHS +# + +GTK_TOP := ../../../win32-dev/gtk_2_0 +GAIM_TOP := ../.. +PERL_LIB_DIR := ../../../win32-dev/perl58 +PERL_INCLUDE := $(PERL_LIB_DIR)/CORE +GAIM_INSTALL_DIR := $(GAIM_TOP)/win32-install-dir +DLL_INSTALL_DIR := $(GAIM_INSTALL_DIR)/plugins + +## +## VARIABLE DEFINITIONS +## + +TARGET = perl + +CC := gcc.exe + +# Compiler Options + +CFLAGS = + +DEFINES = + +## +## INCLUDE MAKEFILES +## + +include $(GAIM_TOP)/src/win32/global.mak + +# Perl headers with /* /* */ type comments.. Turn off warnings. +CFLAGS += -Wno-comment + +## +## INCLUDE PATHS +## + +INCLUDE_PATHS += -I. \ + -I$(GAIM_TOP) \ + -I$(GAIM_TOP)/src \ + -I$(GAIM_TOP)/src/win32 \ + -I$(GTK_TOP)/include \ + -I$(GTK_TOP)/include/gtk-2.0 \ + -I$(GTK_TOP)/include/glib-2.0 \ + -I$(GTK_TOP)/include/pango-1.0 \ + -I$(GTK_TOP)/include/atk-1.0 \ + -I$(GTK_TOP)/lib/glib-2.0/include \ + -I$(GTK_TOP)/lib/gtk-2.0/include \ + -I$(PERL_INCLUDE) + + + + +LIB_PATHS = -L$(GTK_TOP)/lib \ + -L$(GAIM_TOP)/src \ + -L$(PERL_LIB_DIR) + + +## +## SOURCES, OBJECTS +## + +C_SRC = perl.c \ + perl-common.c \ + perl-handlers.c + + +OBJECTS = $(C_SRC:%.c=%.o) + + +## +## LIBRARIES +## + +LIBS = -lgtk-win32-2.0 \ + -lglib-2.0 \ + -lgdk-win32-2.0 \ + -lgmodule-2.0 \ + -lgobject-2.0 \ + -lws2_32 \ + -lintl \ + -lgaim \ + -lperl58 + + +## +## RULES +## + +# How to make a C file + +%.o: %.c + $(CC) $(CFLAGS) $(DEFINES) $(INCLUDE_PATHS) -o $@ -c $< + +## +## TARGET DEFINITIONS +## + +.PHONY: all clean + +all: $(TARGET).dll + $(MAKE) -C ./common -f Makefile.mingw + +install: + cp $(TARGET).dll $(DLL_INSTALL_DIR) + $(MAKE) -C ./common -f Makefile.mingw install + +## +## BUILD Dependencies +## + +$(GAIM_TOP)/src/gaim.lib: + $(MAKE) -C $(GAIM_TOP)/src -f Makefile.mingw gaim.lib + +## +## BUILD DLL +## + +$(TARGET).dll: $(OBJECTS) $(GAIM_TOP)/src/gaim.lib + $(CC) -shared $(OBJECTS) $(LIB_PATHS) $(LIBS) $(DLL_LD_FLAGS) -Wl,--export-all-symbols -Wl,--out-implib,$(TARGET).lib -o $(TARGET).dll + + +## +## CLEAN RULES +## + +clean: + rm -rf *.o + rm -rf $(TARGET).dll $(TARGET).lib + $(MAKE) -C ./common -f Makefile.mingw clean diff -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/common/Account.xs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/common/Account.xs Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,316 @@ +#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_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); + +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 + +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 = gaim_accounts_get_all_active(); 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() + +void * +gaim_accounts_get_handle() + +void +gaim_accounts_init() + +void +gaim_accounts_uninit() diff -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/common/AccountOpts.xs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/common/AccountOpts.xs Sat Dec 16 04:59:55 2006 +0000 @@ -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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/common/BuddyIcon.xs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/common/BuddyIcon.xs Sat Dec 16 04:59:55 2006 +0000 @@ -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(); + +void * +gaim_buddy_icons_get_handle(); + +void +gaim_buddy_icons_init(); + +void +gaim_buddy_icons_uninit() diff -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/common/BuddyList.xs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/common/BuddyList.xs Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,379 @@ +#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_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_set_ui_ops(ops) + Gaim::BuddyList::UiOps ops + +Gaim::BuddyList::UiOps +gaim_blist_get_ui_ops() + +void * +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_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(NULL, NULL); + + 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 + +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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/common/Cipher.xs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/common/Cipher.xs Sat Dec 16 04:59:55 2006 +0000 @@ -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"))); + } + +gpointer +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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/common/Cmds.xs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/common/Cmds.xs Sat Dec 16 04:59:55 2006 +0000 @@ -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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/common/Connection.xs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/common/Connection.xs Sat Dec 16 04:59:55 2006 +0000 @@ -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() + +void * +gaim_connections_get_handle() diff -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/common/Conversation.xs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/common/Conversation.xs Sat Dec 16 04:59:55 2006 +0000 @@ -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 + +void * +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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/common/Debug.xs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/common/Debug.xs Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,42 @@ +#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 + +void +gaim_debug_misc(category, string) + const char *category + const char *string + +void +gaim_debug_info(category, string) + const char *category + const char *string + +void +gaim_debug_warning(category, string) + const char *category + const char *string + +void +gaim_debug_error(category, string) + const char *category + const char *string + +void +gaim_debug_fatal(category, string) + const char *category + const char *string + +void +gaim_debug_set_enabled(enabled) + gboolean enabled + +gboolean +gaim_debug_is_enabled() diff -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/common/FT.xs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/common/FT.xs Sat Dec 16 04:59:55 2006 +0000 @@ -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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/common/Gaim.pm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/common/Gaim.pm Sat Dec 16 04:59:55 2006 +0000 @@ -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, Echipx86@gnupdate.orgE + +=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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/common/Gaim.xs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/common/Gaim.xs Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,90 @@ +#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(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(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(); diff -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/common/ImgStore.xs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/common/ImgStore.xs Sat Dec 16 04:59:55 2006 +0000 @@ -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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/common/Log.xs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/common/Log.xs Sat Dec 16 04:59:55 2006 +0000 @@ -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 + +char * +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"))); + } + +char * +gaim_log_read(log, flags) + Gaim::Log log + Gaim::Log::ReadFlags flags + +gint +gaim_log_set_compare(y, z) + gconstpointer y + gconstpointer z diff -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/common/MANIFEST --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/common/MANIFEST Sat Dec 16 04:59:55 2006 +0000 @@ -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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/common/Makefile.PL.in --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/common/Makefile.PL.in Sat Dec 16 04:59:55 2006 +0000 @@ -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 => 'Christian Hammond ') : ()), + 'LIBS' => [''], # e.g., '-lm' + 'DEFINE' => '@DEBUG_CFLAGS@', # e.g., '-DHAVE_SOMETHING' + 'INC' => '-I. -I@srcdir@ -I@top_srcdir@ -I@top_srcdir@/src @GLIB_CFLAGS@ @GTK_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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/common/Makefile.mingw --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/common/Makefile.mingw Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,142 @@ +# +# Makefile.mingw +# +# Description: Makefile for Gaim perl module. +# + +TARGET = Gaim +AUTOSPLIT = lib/auto/Gaim/autosplit.ix + +## +## TOOLS +## + +CC := gcc +PERL := /cygdrive/c/perl/bin/perl + +## +## PATHS +## + +EXTUTILS := C:/perl/lib/ExtUtils +GAIM_TOP := ../../.. +GTK_TOP := ../../../../win32-dev/gtk_2_0 +PERL_TOP := ../../../../win32-dev/perl58 +PERL_PLUGIN_TOP := .. +GAIM_INSTALL_DIR := $(GAIM_TOP)/win32-install-dir +PERLMOD_INSTALL_DIR := $(GAIM_INSTALL_DIR)/perlmod + + +INCLUDE_PATHS = -I. \ + -I$(GAIM_TOP) \ + -I$(GAIM_TOP)/src \ + -I$(GTK_TOP)/include \ + -I$(GTK_TOP)/include/gtk-2.0 \ + -I$(GTK_TOP)/include/glib-2.0 \ + -I$(GTK_TOP)/include/pango-1.0 \ + -I$(GTK_TOP)/include/atk-1.0 \ + -I$(GTK_TOP)/lib/gtk-2.0/include \ + -I$(GTK_TOP)/lib/glib-2.0/include \ + -I$(PERL_TOP)/CORE + +LIB_PATHS = -L$(PERL_TOP) \ + -L$(PERL_PLUGIN_TOP) \ + -L$(GAIM_TOP)/src \ + -L$(GTK_TOP)/lib + + +## +## SOURCES, OBJECTS +## + +XS_FILES = Account.xs \ + AccountOpts.xs \ + BuddyIcon.xs \ + BuddyList.xs \ + Cipher.xs \ + Cmds.xs \ + Connection.xs \ + Conversation.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 + +## +## RULES +## + +# How to make a C file +%.o: %.c + $(CC) $(CFLAGS) $(INCLUDE_PATHS) $(DEFINES) -c $< -o $@ + +# How to make a XS file +%.c: %.xs + $(PERL) $(EXTUTILS)/xsubpp -typemap $(EXTUTILS)/typemap -typemap typemap $< > $@ + +%.inc: + cp fallback/$@ ./ + +## +## TARGETS +## + +.PHONY: all clean + +all: $(TARGET).dll $(AUTOSPLIT) + +install: + rm -rf $(PERLMOD_INSTALL_DIR) + cp -R lib $(PERLMOD_INSTALL_DIR) + cp $(TARGET).dll $(PERLMOD_INSTALL_DIR) + +$(AUTOSPLIT): Gaim.pm + mkdir -p ./lib/auto + cp Gaim.pm ./lib + $(PERL) -MAutoSplit -e 'autosplit("lib/Gaim.pm")' + +$(TARGET).dll: $(FALLBACKS) $(OBJECTS) + $(CC) -shared $(OBJECTS) $(LIB_PATHS) $(LIBS) -o $(TARGET).dll + +## +## CLEAN +## + +clean: + rm -rf *.o $(TARGET).dll $(FALLBACKS) lib diff -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/common/Network.xs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/common/Network.xs Sat Dec 16 04:59:55 2006 +0000 @@ -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 + +int +gaim_network_listen(port, socket_type, cb, cb_data) + unsigned short port + int socket_type + Gaim::NetworkListenCallback cb + gpointer cb_data + +int +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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/common/Notify.xs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/common/Notify.xs Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,86 @@ +#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 + +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 + +void * +gaim_notify_userinfo(gc, who, text, cb, user_data) + Gaim::Connection gc + const char *who + const char *text + Gaim::NotifyCloseCallback cb + gpointer user_data diff -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/common/Plugin.xs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/common/Plugin.xs Sat Dec 16 04:59:55 2006 +0000 @@ -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"))); + } + +void * +gaim_plugins_get_handle() + +void +gaim_plugins_init() + +void +gaim_plugins_uninit() diff -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/common/PluginPref.xs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/common/PluginPref.xs Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,142 @@ +#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 + gpointer choice + +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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/common/Pounce.xs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/common/Pounce.xs Sat Dec 16 04:59:55 2006 +0000 @@ -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"))); + } + +void * +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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/common/Prefs.xs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/common/Prefs.xs Sat Dec 16 04:59:55 2006 +0000 @@ -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 + +void * +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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/common/Privacy.xs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/common/Privacy.xs Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,14 @@ +#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 diff -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/common/Proxy.xs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/common/Proxy.xs Sat Dec 16 04:59:55 2006 +0000 @@ -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() + +void * +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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/common/Prpl.xs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/common/Prpl.xs Sat Dec 16 04:59:55 2006 +0000 @@ -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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/common/Request.xs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/common/Request.xs Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,575 @@ +#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; +} + +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; +} + +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, *package; + + basename = g_path_get_basename(handle->path); + gaim_perl_normalize_script_name(basename); + package = g_strdup_printf("Gaim::Script::%s", basename); + gpr = g_new(GaimPerlRequestData, 1); + gpr->ok_cb = g_strdup_printf("%s::%s", package, SvPV(ok_cb, len)); + gpr->cancel_cb = g_strdup_printf("%s::%s", package, SvPV(cancel_cb, len)); + + 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, *package; + + basename = g_path_get_basename(handle->path); + gaim_perl_normalize_script_name(basename); + package = g_strdup_printf("Gaim::Script::%s", basename); + gpr = g_new(GaimPerlRequestData, 1); + gpr->ok_cb = g_strdup_printf("%s::%s", package, SvPV(ok_cb, len)); + gpr->cancel_cb = g_strdup_printf("%s::%s", package, SvPV(cancel_cb, len)); + + 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, *package; + + basename = g_path_get_basename(handle->path); + gaim_perl_normalize_script_name(basename); + package = g_strdup_printf("Gaim::Script::%s", basename); + gpr = g_new(GaimPerlRequestData, 1); + gpr->ok_cb = g_strdup_printf("%s::%s", package, SvPV(ok_cb, len)); + gpr->cancel_cb = g_strdup_printf("%s::%s", package, SvPV(cancel_cb, len)); + + 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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/common/Roomlist.xs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/common/Roomlist.xs Sat Dec 16 04:59:55 2006 +0000 @@ -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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/common/SSLConn.xs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/common/SSLConn.xs Sat Dec 16 04:59:55 2006 +0000 @@ -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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/common/SavedStatuses.xs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/common/SavedStatuses.xs Sat Dec 16 04:59:55 2006 +0000 @@ -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"))); + } + +void * +gaim_savedstatuses_get_handle() + +void +gaim_savedstatuses_init() + +void +gaim_savedstatuses_uninit() diff -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/common/Server.xs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/common/Server.xs Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,221 @@ +#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(NULL, NULL); + + 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(NULL, NULL); + + 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(NULL, NULL); + + 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_buddyicon(gc, filename) + Gaim::Connection gc + const char *filename + +void +serv_set_info(con, a) + Gaim::Connection con + const char * a + +void +serv_set_permit_deny(con) + Gaim::Connection con + diff -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/common/Signal.xs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/common/Signal.xs Sat Dec 16 04:59:55 2006 +0000 @@ -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) + void *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) + void *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) + void *instance + const char *signal + Gaim::Plugin plugin +CODE: + gaim_perl_signal_disconnect(plugin, instance, signal); diff -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/common/Sound.xs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/common/Sound.xs Sat Dec 16 04:59:55 2006 +0000 @@ -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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/common/Status.xs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/common/Status.xs Sat Dec 16 04:59:55 2006 +0000 @@ -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 + +void * +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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/common/Stringref.xs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/common/Stringref.xs Sat Dec 16 04:59:55 2006 +0000 @@ -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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/common/Util.xs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/common/Util.xs Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,264 @@ +#include "module.h" + +typedef struct { + char *cb; +} GaimPerlUrlData; + +static void gaim_perl_util_url_cb(void *data, const char *url_data, size_t size) { + GaimPerlUrlData *gpr = (GaimPerlUrlData *)data; + dSP; + ENTER; + SAVETMPS; + PUSHMARK(sp); + + XPUSHs(sv_2mortal(newSVpv(url_data, 0))); + PUTBACK; + + call_pv(gpr->cb, G_EVAL | G_SCALAR); + SPAGAIN; + + PUTBACK; + FREETMPS; + LEAVE; +} + +MODULE = Gaim::Util PACKAGE = Gaim::Util PREFIX = gaim_ +PROTOTYPES: ENABLE + +void +gaim_url_fetch(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, *package; + + basename = g_path_get_basename(handle->path); + gaim_perl_normalize_script_name(basename); + package = g_strdup_printf("Gaim::Script::%s", basename); + gpr = g_new(GaimPerlUrlData, 1); + + gpr->cb = g_strdup_printf("%s::%s", package, SvPV(cb, len)); + gaim_url_fetch(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 + +char * +gaim_fd_get_ip(fd) + int fd + +const gchar * +gaim_home_dir() + +gboolean +gaim_markup_extract_info_field(str, len, dest, start_token, skip, end_token, check_value, no_value_token, display_name, is_link, link_prefix, format_cb) + const char *str + int len + GString *dest + 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 + +char * +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 + +char * +gaim_markup_linkify(str) + const char *str + +char * +gaim_markup_slice(str, x, y) + const char *str + guint x + guint y + +char * +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 + +char * +gaim_str_add_cr(str) + const char *str + +char * +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 + +char * +gaim_str_seconds_to_string(sec) + guint sec + +char * +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 * +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 * +gaim_strdup_withhtml(src) + const gchar *src + +gchar * +gaim_strreplace(string, delimiter, replacement) + const char *string + const char *delimiter + const char *replacement + +char * +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 + +char * +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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/common/XMLNode.xs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/common/XMLNode.xs Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,88 @@ +#include "module.h" + +MODULE = Gaim::XMLNode PACKAGE = Gaim::XMLNode PREFIX = xmlnode_ +PROTOTYPES: ENABLE + +Gaim::XMLNode +xmlnode_copy(class, src) + xmlnode *src + C_ARGS: + src + +void +xmlnode_free(node) + 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) + xmlnode *node + const char *attr + +Gaim::XMLNode +xmlnode_get_child(parent, name) + const xmlnode *parent + const char *name + +Gaim::XMLNode +xmlnode_get_child_with_namespace(parent, name, xmlns) + const xmlnode *parent + const char *name + const char *xmlns + +char * +xmlnode_get_data(node) + xmlnode *node + +Gaim::XMLNode +xmlnode_get_next_twin(node) + xmlnode *node + +void +xmlnode_insert_child(parent, child) + xmlnode *parent + xmlnode *child + +void +xmlnode_insert_data(node, data, size) + 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) + xmlnode *parent + const char *name + +void +xmlnode_remove_attrib(node, attr) + xmlnode *node + const char *attr + +void +xmlnode_set_attrib(node, attr, value) + xmlnode *node + const char *attr + const char *value + +char * +xmlnode_to_formatted_str(node, len) + xmlnode *node + int *len + +char * +xmlnode_to_str(node, len) + xmlnode *node + int *len diff -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/common/fallback/const-c.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/common/fallback/const-c.inc Sat Dec 16 04:59:55 2006 +0000 @@ -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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/common/fallback/const-xs.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/common/fallback/const-xs.inc Sat Dec 16 04:59:55 2006 +0000 @@ -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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/common/module.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/common/module.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,260 @@ + + +typedef struct group *Gaim__Group; + +#define group perl_group + +#include +#ifdef _WIN32 +#undef pipe +#endif +#include +#include +#include + +#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 "debug.h" +#include "desktopitem.h" +#include "eventloop.h" +#include "ft.h" +#include "gtkaccount.h" +#include "gtkblist.h" +#include "gtkconn.h" +#include "gtkconv.h" +#include "gtkutils.h" +#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 "src/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 GaimUnseenState Gaim__UnseenState; +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; + +/* 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; + +/* 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 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 */ + +/* 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 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; + +/* 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 GaimMenuAction * Gaim__Menu__Action; + +/* value.h */ +typedef GaimValue * Gaim__Value; + +/* xmlnode.h */ +typedef xmlnode * Gaim__XMLNode; +typedef XMLNodeType XMLNode__Type; + +/* other.h */ diff -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/common/typemap --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/common/typemap Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,205 @@ +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 +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 +Gaim::GTK::Widget * 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::Desktop::Item T_GaimObj +Gaim::DesktopItemType T_IV + +Gaim::GTK::BuddyList T_GaimObj +Gaim::GDK::Pixbuf T_GaimObj +Gaim::GTK::Conversation T_GaimObj +Gaim::GTK::Widget T_GaimObj +Gaim::GTK::FileSelection T_GaimObj +Gaim::GTK::SelectionData T_GaimObj +Gaim::GTK::TextView 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::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::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::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 + +/* gtkutils.h */ +Gaim::ButtonOrientation T_IV +Gaim::ButtonStyle T_IV +Gaim::BrowserPlace T_IV + +INPUT + +T_GaimObj + $var = gaim_perl_ref_object($arg) + +OUTPUT + +T_GaimObj + $arg = gaim_perl_bless_object($var, \"$type\"); diff -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/libgaimperl.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/libgaimperl.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,9 @@ +#include +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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/perl-common.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/perl-common.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,615 @@ +#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 + 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 + 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 + 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; + + 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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/perl-common.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/perl-common.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,59 @@ +#ifndef _GAIM_PERL_COMMON_H_ +#define _GAIM_PERL_COMMON_H_ + +#include +#ifdef _WIN32 +#undef pipe +#endif +#include +#include +#include + +#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; + char *gtk_prefs_sub; + 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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/perl-handlers.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/perl-handlers.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,633 @@ +#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; +} + +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; +} + +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, + ©_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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/perl-handlers.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/perl-handlers.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,67 @@ +#ifndef _GAIM_PERL_HANDLERS_H_ +#define _GAIM_PERL_HANDLERS_H_ + +#include "cmds.h" +#include "plugin.h" +#include "prefs.h" +#include "pluginpref.h" +#include "gtkplugin.h" +#include "gtkutils.h" + +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); + +GtkWidget *gaim_perl_gtk_get_plugin_frame(GaimPlugin *plugin); + +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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/perl.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/perl.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,606 @@ +/* + * gaim + * + * 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 + */ +#ifdef HAVE_CONFIG_H +#include +# ifdef HAVE_LIMITS_H +# include +# 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 + +#ifndef _SEM_SEMUN_UNDEFINED +# define HAS_UNION_SEMUN +#endif + +#include +#include + +#ifndef _WIN32 +# include +#endif + +#undef PACKAGE + +#ifndef _WIN32 +# include +#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) */ +}; + +static GaimGtkPluginUiInfo gtk_ui_info = +{ + gaim_perl_gtk_get_plugin_frame, + 0 /* page_num (Reserved) */ +}; + +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__\";" + "$_=;" + "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; + + gaim_debug(GAIM_DEBUG_INFO, "perl", + "Found plugin info\n"); + + 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)); + + if ((key = hv_fetch(plugin_info, "GTK_UI", + strlen("GTK_UI"), 0))) + info->ui_requirement = GAIM_GTK_PLUGIN_TYPE; + + 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; + } + + 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 = >k_ui_info; + } + + 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); + g_free(gps->gtk_prefs_sub); + 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 ", /**< 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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/scripts/account.pl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/scripts/account.pl Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,125 @@ +$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 ", + 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 = "perlTestGroup"; + my $TEST_NAME = "perlTestName"; + my $TEST_ALIAS = "perlTestAlias"; + 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("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("plugin_load()", "Testing $MODULE_NAME Completed."); + print "\n\n" . "#" x 80 . "\n\n"; +} + +sub plugin_unload { + my $plugin = shift; + + print "#" x 80 . "\n\n"; + Gaim::debug_info("plugin_unload()", "Testing $MODULE_NAME Started."); + print "\n\n"; + + ######### TEST CODE HERE ########## + + print "\n\n"; + Gaim::debug_info("plugin_unload()", "Testing $MODULE_NAME Completed."); + print "\n\n" . "#" x 80 . "\n\n"; +} + diff -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/scripts/buddy_list.pl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/scripts/buddy_list.pl Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,101 @@ +$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 ", + 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 ########## + + print "Testing: Gaim::Find::buddy()..."; + $buddy = Gaim::Find::buddy($account, $TEST_NAME); + if ($buddy) { print "ok.\n"; } else { print "fail.\n"; } + + print "Testing: Gaim::BuddyList::get_handle()..."; + $handle = Gaim::BuddyList::get_handle(); + if ($handle) { print "ok.\n"; } else { print "fail.\n"; } + + print "Testing: Gaim::BuddyList::get_blist()..."; + $blist = Gaim::BuddyList::get_blist(); + if ($blist) { print "ok.\n"; } else { print "fail.\n"; } + + print "Testing: Gaim::Buddy::new..."; + $buddy = Gaim::Buddy::new($account, $TEST_NAME . "TEST", $TEST_ALIAS); + if ($buddy) { print "ok.\n"; } else { print "fail.\n"; } + + print "Testing: Gaim::Find::group..."; + $group = Gaim::Find::group($TEST_GROUP); + if ($group) { print "ok.\n"; } else { print "fail.\n"; } + + print "Testing: Gaim::BuddyList::add_buddy..."; + Gaim::BuddyList::add_buddy($buddy, undef, $group, $group); + if ($buddy) { print "ok.\n"; } else { print "fail.\n"; } + + print "Testing: Gaim::Find::buddies...\n"; + @buddy_array = Gaim::Find::buddies($account, $USERNAME); + if (@buddy_array) { + print "Buddies in list (" . @buddy_array . "): \n"; + foreach $bud (@buddy_array) { + print Gaim::Buddy::get_name($bud) . "\n"; + } + } + + print "#" x 80 . "\n\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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/scripts/conversation.pl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/scripts/conversation.pl Sat Dec 16 04:59:55 2006 +0000 @@ -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 ", + 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", "Message 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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/scripts/count_down.pl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/scripts/count_down.pl Sat Dec 16 04:59:55 2006 +0000 @@ -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 ", + 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 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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/scripts/gtk_frame_test.pl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/scripts/gtk_frame_test.pl Sat Dec 16 04:59:55 2006 +0000 @@ -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 ", + 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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/scripts/plugin_action.pl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/scripts/plugin_action.pl Sat Dec 16 04:59:55 2006 +0000 @@ -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 ", + url => "http://sourceforge.net/users/deryni9/", + + load => "plugin_load", + unload => "plugin_unload", + plugin_action_sub => "plugin_action_names" +); diff -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/scripts/plugin_pref.pl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/scripts/plugin_pref.pl Sat Dec 16 04:59:55 2006 +0000 @@ -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 ", + 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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/perl/scripts/request.pl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/perl/scripts/request.pl Sat Dec 16 04:59:55 2006 +0000 @@ -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 ", + url => "http://sourceforge.net/users/johnhkelm/", + + load => "plugin_load", + unload => "plugin_unload", + plugin_action => "plugin_action_test", + plugin_action_label => "Plugin Action Test Label" +); + + + # 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 plugin_init { + return %PLUGIN_INFO; +} + +sub ok_cb_test{ + $fields = shift; + print "ok_cb_test: BEGIN\n"; + print "ok_cb_test: Button Click\n"; + print "ok_cb_test: Field Type: " . $fields . "\n"; + $account = Gaim::Request::fields_get_account($fields, "acct_test"); + print "ok_cb_test: Username of selected account: " . Gaim::Account::get_username($account) . "\n"; + $int = Gaim::Request::fields_get_integer($fields, "int_test"); + print "ok_cb_test: Integer Value:" . $int . "\n"; + $choice = Gaim::Request::fields_get_choice($fields, "ch_test"); + print "ok_cb_test: Choice Value:" . $choice . "\n"; + print "ok_cb_test: END\n"; +} + +sub cancel_cb_test{ + print "cancel_cb_test: Button Click\n"; +} + +sub plugin_action_test { + $plugin = shift; + print "plugin_action_cb_test: BEGIN\n"; + plugin_request($plugin); + print "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; + print "#" x 80 . "\n"; + ######### TEST CODE HERE ########## + + + + print "\n" . "#" x 80 . "\n"; +} + diff -r d10dda2777a9 -r b63ebf84c42b core/plugins/pluginpref_example.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/pluginpref_example.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,162 @@ +/* + * PluginPref Example Plugin + * + * Copyright (C) 2004, Gary Kramlich + * + * 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 +#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 ", /**< 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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/psychic.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/psychic.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,157 @@ + + +#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 " + + +#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" + + +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); + gaim_conversation_present(gconv); + + if(gaim_prefs_get_bool(PREF_NOTICE)) { + gaim_conversation_write(gconv, NULL, + /* 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." */ + _("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); + + 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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/signals-test.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/signals-test.c Sat Dec 16 04:59:55 2006 +0000 @@ -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 + +#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, int *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, int 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, int *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, int 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 ", /**< 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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/simple.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/simple.c Sat Dec 16 04:59:55 2006 +0000 @@ -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 ", /**< 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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/ssl/Makefile.am --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/ssl/Makefile.am Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,34 @@ +EXTRA_DIST = \ + Makefile.mingw + +plugindir = $(libdir)/gaim + +ssl_la_LDFLAGS = -module -avoid-version $(GLIB_LIBS) +ssl_gnutls_la_LDFLAGS = -module -avoid-version $(GLIB_LIBS) +ssl_nss_la_LDFLAGS = -module -avoid-version $(GLIB_LIBS) + +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_gnutls_la_LIBADD = $(GNUTLS_LIBS) +ssl_nss_la_LIBADD = $(NSS_LIBS) + +endif # PLUGINS + +AM_CPPFLAGS = \ + -DDATADIR=\"$(datadir)\" \ + -DLIBDIR=\"$(libdir)/gaim/\" \ + -I$(top_srcdir)/core \ + $(DEBUG_CFLAGS) \ + $(GLIB_CFLAGS) \ + $(PLUGIN_CFLAGS) \ + $(NSS_CFLAGS) \ + $(GNUTLS_CFLAGS) diff -r d10dda2777a9 -r b63ebf84c42b core/plugins/ssl/Makefile.mingw --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/ssl/Makefile.mingw Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,145 @@ +# +# Makefile.mingw +# +# Description: Makefile for ssl plugin. +# + +# +# PATHS +# + +GTK_TOP := ../../../win32-dev/gtk_2_0 +GAIM_TOP := ../.. +GAIM_INSTALL_DIR := $(GAIM_TOP)/win32-install-dir +DLL_INSTALL_DIR := $(GAIM_INSTALL_DIR)/plugins +NSS_DIR := ../../../win32-dev/nss-3.9 +NSPR_DIR := ../../../win32-dev/nspr-4.4.1 + +## +## VARIABLE DEFINITIONS +## + +TARGET = ssl +TARGET_NSS = ssl-nss + +NEEDED_DLLS = $(NSS_DIR)/lib/nss3.dll \ + $(NSS_DIR)/lib/nssckbi.dll \ + $(NSS_DIR)/lib/softokn3.dll \ + $(NSS_DIR)/lib/ssl3.dll \ + $(NSPR_DIR)/lib/nspr4.dll \ + $(NSPR_DIR)/lib/plc4.dll \ + $(NSPR_DIR)/lib/plds4.dll + +# Compiler Options + +CFLAGS = + +DEFINES = + +## +## INCLUDE MAKEFILES +## + +include $(GAIM_TOP)/src/win32/global.mak + +## +## INCLUDE PATHS +## + +INCLUDE_PATHS += -I. \ + -I$(GTK_TOP)/include \ + -I$(GTK_TOP)/include/gtk-2.0 \ + -I$(GTK_TOP)/include/glib-2.0 \ + -I$(GTK_TOP)/include/pango-1.0 \ + -I$(GTK_TOP)/include/atk-1.0 \ + -I$(GTK_TOP)/lib/glib-2.0/include \ + -I$(GTK_TOP)/lib/gtk-2.0/include \ + -I$(GAIM_TOP)/src \ + -I$(GAIM_TOP)/src/win32 \ + -I$(GAIM_TOP) \ + -I$(NSS_DIR)/include \ + -I$(NSPR_DIR)/include + +LIB_PATHS = -L$(GTK_TOP)/lib \ + -L$(GAIM_TOP)/src \ + -L$(NSS_DIR)/lib \ + -L$(NSPR_DIR)/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 = -lgtk-win32-2.0 \ + -lglib-2.0 \ + -lgdk-win32-2.0 \ + -lgmodule-2.0 \ + -lgobject-2.0 \ + -lws2_32 \ + -lintl \ + -lgaim \ + -lnss3 \ + -lnspr4 \ + -lssl3 + + +## +## RULES +## + +# How to make a C file + +%.o: %.c + $(CC) $(CFLAGS) $(DEFINES) $(INCLUDE_PATHS) -o $@ -c $< + +## +## TARGET DEFINITIONS +## + +.PHONY: all clean + +all: $(TARGET).dll $(TARGET_NSS).dll + +install: + cp $(TARGET).dll $(DLL_INSTALL_DIR) + cp $(TARGET_NSS).dll $(DLL_INSTALL_DIR) + cp $(NEEDED_DLLS) $(GAIM_INSTALL_DIR) + +## +## BUILD Dependencies +## + +$(GAIM_TOP)/src/gaim.lib: + $(MAKE) -C $(GAIM_TOP)/src -f Makefile.mingw gaim.lib + +## +## BUILD DLL +## + +$(TARGET).dll: $(OBJECTS) $(GAIM_TOP)/src/gaim.lib + $(CC) -shared $(OBJECTS) $(LIB_PATHS) $(LIBS) $(DLL_LD_FLAGS) -o $(TARGET).dll + +$(TARGET_NSS).dll: $(OBJECTS_NSS) $(GAIM_TOP)/src/gaim.lib + $(CC) -shared $(OBJECTS_NSS) $(LIB_PATHS) $(LIBS) $(DLL_LD_FLAGS) -o $(TARGET_NSS).dll + +## +## CLEAN RULES +## + +clean: + rm -rf *.o + rm -rf $(TARGET).dll + rm -rf $(TARGET_NSS).dll diff -r d10dda2777a9 -r b63ebf84c42b core/plugins/ssl/ssl-gnutls.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/ssl/ssl-gnutls.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,264 @@ +/** + * @file ssl-gnutls.c GNUTLS SSL plugin. + * + * gaim + * + * 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 "sslconn.h" +#include "version.h" + +#define SSL_GNUTLS_PLUGIN_ID "ssl-gnutls" + +#ifdef HAVE_GNUTLS + +#include + +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 ", + 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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/ssl/ssl-nss.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/ssl/ssl-nss.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,433 @@ +/** + * @file ssl-nss.c Mozilla NSS SSL plugin. + * + * gaim + * + * 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 "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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 ", + 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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/ssl/ssl.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/ssl/ssl.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,118 @@ +/** + * @file ssl.c Main SSL plugin + * + * gaim + * + * 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 "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 ", + 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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/statenotify.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/statenotify.c Sat Dec 16 04:59:55 2006 +0000 @@ -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 ", /**< 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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/tcl/Makefile.am --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/tcl/Makefile.am Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,20 @@ +plugindir = $(libdir)/gaim + +tcl_la_LDFLAGS = -module -avoid-version $(GLIB_LIBS) $(TCL_LIBS) $(TK_LIBS) + +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 + +EXTRA_DIST = signal-test.tcl Makefile.mingw + +AM_CPPFLAGS = \ + -DVERSION=\"$(VERSION)\" \ + -I$(top_srcdir) \ + -I$(top_srcdir)/core \ + $(DEBUG_CFLAGS) \ + $(GLIB_CFLAGS) \ + $(PLUGIN_CFLAGS) \ + $(TK_CFLAGS) \ + $(TCL_CFLAGS) diff -r d10dda2777a9 -r b63ebf84c42b core/plugins/tcl/Makefile.mingw --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/tcl/Makefile.mingw Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,131 @@ +# +# Makefile.mingw +# +# Description: Makefile for tcl plugin loader plugin. +# + +# +# PATHS +# + +GTK_TOP := ../../../win32-dev/gtk_2_0 +GAIM_TOP := ../.. +TCL_LIB_DIR := ../../../win32-dev/tcl-8.4.5 +TCL_INC_DIR := $(TCL_LIB_DIR)/include +GAIM_INSTALL_DIR := $(GAIM_TOP)/win32-install-dir +DLL_INSTALL_DIR := $(GAIM_INSTALL_DIR)/plugins + +## +## VARIABLE DEFINITIONS +## + +TARGET = tcl + +# Compiler Options + +CFLAGS = + +DEFINES = -DHAVE_TK -DUSE_TCL_STUBS -DUSE_TK_STUBS + +## +## INCLUDE MAKEFILES +## + +include $(GAIM_TOP)/src/win32/global.mak + +## +## INCLUDE PATHS +## + +INCLUDE_PATHS += -I. \ + -I$(GAIM_TOP) \ + -I$(GAIM_TOP)/src \ + -I$(GAIM_TOP)/src/win32 \ + -I$(GTK_TOP)/include \ + -I$(GTK_TOP)/include/gtk-2.0 \ + -I$(GTK_TOP)/include/glib-2.0 \ + -I$(GTK_TOP)/include/pango-1.0 \ + -I$(GTK_TOP)/include/atk-1.0 \ + -I$(GTK_TOP)/lib/glib-2.0/include \ + -I$(GTK_TOP)/lib/gtk-2.0/include \ + -I$(TCL_INC_DIR) + + +LIB_PATHS = -L$(GTK_TOP)/lib \ + -L$(GAIM_TOP)/src \ + -L$(TCL_LIB_DIR) + + +## +## 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 = -lgtk-win32-2.0 \ + -lglib-2.0 \ + -lgdk-win32-2.0 \ + -lgmodule-2.0 \ + -lgobject-2.0 \ + -lws2_32 \ + -lintl \ + -lgaim \ + -ltclstub84 \ + -ltkstub84 + + +## +## RULES +## + +# How to make a C file + +%.o: %.c + $(CC) $(CFLAGS) $(DEFINES) $(INCLUDE_PATHS) -o $@ -c $< + +## +## TARGET DEFINITIONS +## + +.PHONY: all clean + +all: $(TARGET).dll + +install: $(TARGET).dll + cp $(TARGET).dll $(DLL_INSTALL_DIR) + +## +## BUILD Dependencies +## + +$(GAIM_TOP)/src/gaim.lib: + $(MAKE) -C $(GAIM_TOP)/src -f Makefile.mingw gaim.lib + +## +## BUILD DLL +## + +$(TARGET).dll: $(OBJECTS) $(GAIM_TOP)/src/gaim.lib + $(CC) -shared $(OBJECTS) $(LIB_PATHS) $(LIBS) $(DLL_LD_FLAGS) -o $(TARGET).dll + + +## +## CLEAN RULES +## + +clean: + rm -rf *.o + rm -rf $(TARGET).dll diff -r d10dda2777a9 -r b63ebf84c42b core/plugins/tcl/signal-test.tcl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/tcl/signal-test.tcl Sat Dec 16 04:59:55 2006 +0000 @@ -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 " \ + "http://gaim.sourceforge.net/" +} diff -r d10dda2777a9 -r b63ebf84c42b core/plugins/tcl/tcl.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/tcl/tcl.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,513 @@ +/** + * @file tcl.c Gaim Tcl plugin bindings + * + * gaim + * + * Copyright (C) 2003 Ethan Blanton + * + * 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 + +#ifdef HAVE_TK +#include +#endif + +#include +#include +#include +#include +#include + +#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 ", + 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() { + gaim_debug(GAIM_DEBUG_INFO, "tcl", + "Initializing the Tcl runtime. If Gaim doesn't load, it is " + "most likely because you have cygwin in your PATH and you " + "should remove it. See http://gaim.sf.net/win32 for more " + "information\n"); + + if(!(wtcl_CreateInterp = (LPFNTCLCREATEINTERP) wgaim_find_and_loadproc("tcl84.dll", "Tcl_CreateInterp"))) { + gaim_debug(GAIM_DEBUG_INFO, "tcl", "tcl_win32_init error loading Tcl_CreateInterp\n"); + return FALSE; + } + + if(!(wtk_Init = (LPFNTKINIT) wgaim_find_and_loadproc("tk84.dll", "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); + return FALSE; + } + + if (GetModuleHandle("cygwin1.dll")) { + HMODULE mod; + gaim_debug(GAIM_DEBUG_INFO, "tcl", "Cygwin has been loaded by tcl84.dll and/or tk84.dll. Disabling Tcl support to avoid problems.\n"); + if((mod = GetModuleHandle("tcl84.dll"))) + FreeLibrary(mod); + if((mod = GetModuleHandle("tk84.dll"))) + FreeLibrary(mod); + return FALSE; + } + + return TRUE; +} + +#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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/tcl/tcl_cmd.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/tcl/tcl_cmd.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,190 @@ +/** + * @file tcl_cmd.c Gaim Tcl cmd API + * + * gaim + * + * Copyright (C) 2006 Etan Reisner + * + * 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 + +#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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/tcl/tcl_cmds.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/tcl/tcl_cmds.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,1574 @@ +/** + * @file tcl_cmds.c Commands for the Gaim Tcl plugin bindings + * + * gaim + * + * Copyright (C) 2003 Ethan Blanton + * + * 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 + +#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", NULL }; + enum { CMD_CONV_FIND, CMD_CONV_HANDLE, CMD_CONV_LIST, CMD_CONV_NEW, CMD_CONV_WRITE } 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; + } + + 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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/tcl/tcl_gaim.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/tcl/tcl_gaim.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,116 @@ +/** + * @file tcl_gaim.h Gaim Tcl definitions + * + * gaim + * + * Copyright (C) 2003 Ethan Blanton + * + * 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 + +#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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/tcl/tcl_glib.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/tcl/tcl_glib.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,254 @@ +/* + * Tcl/Glib glue + * + * Copyright (C) 2003, 2004, 2006 Ethan Blanton + * + * 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 +#include +#include + +#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(¬ifier, 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(¬ifier); + 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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/tcl/tcl_glib.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/tcl/tcl_glib.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,29 @@ +/* + * Tcl/Glib glue + * + * Copyright (C) 2003 Ethan Blanton + * + * 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 +#include + +void tcl_glib_init(void); + +#endif /* _GAIM_TCL_GLIB_H_ */ diff -r d10dda2777a9 -r b63ebf84c42b core/plugins/tcl/tcl_ref.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/tcl/tcl_ref.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,152 @@ +/** + * @file tcl_ref.c Gaim Tcl typed references API + * + * gaim + * + * Copyright (C) 2006 Ethan Blanton + * + * 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 +#include + +#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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/tcl/tcl_signals.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/tcl/tcl_signals.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,395 @@ +/** + * @file tcl_signals.c Gaim Tcl signal API + * + * gaim + * + * Copyright (C) 2003 Ethan Blanton + * + * 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 +#include + +#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)); + } + 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 -r d10dda2777a9 -r b63ebf84c42b core/plugins/test.pl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/plugins/test.pl Sat Dec 16 04:59:55 2006 +0000 @@ -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 ', + 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; +} diff -r d10dda2777a9 -r b63ebf84c42b core/pounce.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/pounce.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,1136 @@ +/** + * @file pounce.c Buddy Pounce API + * @ingroup core + * + * 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 "conversation.h" +#include "debug.h" +#include "pounce.h" + +#include "debug.h" +#include "pounce.h" +#include "util.h" + +typedef struct +{ + GString *buffer; + + GaimPounce *pounce; + GaimPounceEvent events; + GaimPounceOption options; + + char *ui_name; + char *pouncee; + char *protocol_id; + char *event_type; + char *option_type; + char *action_name; + char *param_name; + char *account_name; + +} PounceParserData; + +typedef struct +{ + char *name; + + gboolean enabled; + + GHashTable *atts; + +} GaimPounceActionData; + +typedef struct +{ + char *ui; + GaimPounceCb cb; + void (*new_pounce)(GaimPounce *); + void (*free_pounce)(GaimPounce *); + +} GaimPounceHandler; + + +static GHashTable *pounce_handlers = NULL; +static GList *pounces = NULL; +static guint save_timer = 0; +static gboolean pounces_loaded = FALSE; + + +/********************************************************************* + * Private utility functions * + *********************************************************************/ + +static GaimPounceActionData * +find_action_data(const GaimPounce *pounce, const char *name) +{ + GaimPounceActionData *action; + + g_return_val_if_fail(pounce != NULL, NULL); + g_return_val_if_fail(name != NULL, NULL); + + action = g_hash_table_lookup(pounce->actions, name); + + return action; +} + +static void +free_action_data(gpointer data) +{ + GaimPounceActionData *action_data = data; + + g_free(action_data->name); + + g_hash_table_destroy(action_data->atts); + + g_free(action_data); +} + + +/********************************************************************* + * Writing to disk * + *********************************************************************/ + +static void +action_parameter_to_xmlnode(gpointer key, gpointer value, gpointer user_data) +{ + const char *name, *param_value; + xmlnode *node, *child; + + name = (const char *)key; + param_value = (const char *)value; + node = (xmlnode *)user_data; + + child = xmlnode_new_child(node, "param"); + xmlnode_set_attrib(child, "name", name); + xmlnode_insert_data(child, param_value, -1); +} + +static void +action_parameter_list_to_xmlnode(gpointer key, gpointer value, gpointer user_data) +{ + const char *action; + GaimPounceActionData *action_data; + xmlnode *node, *child; + + action = (const char *)key; + action_data = (GaimPounceActionData *)value; + node = (xmlnode *)user_data; + + if (!action_data->enabled) + return; + + child = xmlnode_new_child(node, "action"); + xmlnode_set_attrib(child, "type", action); + + g_hash_table_foreach(action_data->atts, action_parameter_to_xmlnode, child); +} + +static void +add_event_to_xmlnode(xmlnode *node, const char *type) +{ + xmlnode *child; + + child = xmlnode_new_child(node, "event"); + xmlnode_set_attrib(child, "type", type); +} + +static void +add_option_to_xmlnode(xmlnode *node, const char *type) +{ + xmlnode *child; + + child = xmlnode_new_child(node, "option"); + xmlnode_set_attrib(child, "type", type); +} + +static xmlnode * +pounce_to_xmlnode(GaimPounce *pounce) +{ + xmlnode *node, *child; + GaimAccount *pouncer; + GaimPounceEvent events; + GaimPounceOption options; + + pouncer = gaim_pounce_get_pouncer(pounce); + events = gaim_pounce_get_events(pounce); + options = gaim_pounce_get_options(pounce); + + node = xmlnode_new("pounce"); + xmlnode_set_attrib(node, "ui", pounce->ui_type); + + child = xmlnode_new_child(node, "account"); + xmlnode_set_attrib(child, "protocol", pouncer->protocol_id); + xmlnode_insert_data(child, gaim_account_get_username(pouncer), -1); + + child = xmlnode_new_child(node, "pouncee"); + xmlnode_insert_data(child, gaim_pounce_get_pouncee(pounce), -1); + + /* Write pounce options */ + child = xmlnode_new_child(node, "options"); + if (options & GAIM_POUNCE_OPTION_AWAY) + add_option_to_xmlnode(child, "on-away"); + + /* Write pounce events */ + child = xmlnode_new_child(node, "events"); + if (events & GAIM_POUNCE_SIGNON) + add_event_to_xmlnode(child, "sign-on"); + if (events & GAIM_POUNCE_SIGNOFF) + add_event_to_xmlnode(child, "sign-off"); + if (events & GAIM_POUNCE_AWAY) + add_event_to_xmlnode(child, "away"); + if (events & GAIM_POUNCE_AWAY_RETURN) + add_event_to_xmlnode(child, "return-from-away"); + if (events & GAIM_POUNCE_IDLE) + add_event_to_xmlnode(child, "idle"); + if (events & GAIM_POUNCE_IDLE_RETURN) + add_event_to_xmlnode(child, "return-from-idle"); + if (events & GAIM_POUNCE_TYPING) + add_event_to_xmlnode(child, "start-typing"); + if (events & GAIM_POUNCE_TYPED) + add_event_to_xmlnode(child, "typed"); + if (events & GAIM_POUNCE_TYPING_STOPPED) + add_event_to_xmlnode(child, "stop-typing"); + if (events & GAIM_POUNCE_MESSAGE_RECEIVED) + add_event_to_xmlnode(child, "message-received"); + + /* Write pounce actions */ + child = xmlnode_new_child(node, "actions"); + g_hash_table_foreach(pounce->actions, action_parameter_list_to_xmlnode, child); + + if (gaim_pounce_get_save(pounce)) + child = xmlnode_new_child(node, "save"); + + return node; +} + +static xmlnode * +pounces_to_xmlnode(void) +{ + xmlnode *node, *child; + GList *cur; + + node = xmlnode_new("pounces"); + xmlnode_set_attrib(node, "version", "1.0"); + + for (cur = gaim_pounces_get_all(); cur != NULL; cur = cur->next) + { + child = pounce_to_xmlnode(cur->data); + xmlnode_insert_child(node, child); + } + + return node; +} + +static void +sync_pounces(void) +{ + xmlnode *node; + char *data; + + if (!pounces_loaded) + { + gaim_debug_error("pounce", "Attempted to save buddy pounces before " + "they were read!\n"); + return; + } + + node = pounces_to_xmlnode(); + data = xmlnode_to_formatted_str(node, NULL); + gaim_util_write_data_to_file("pounces.xml", data, -1); + g_free(data); + xmlnode_free(node); +} + +static gboolean +save_cb(gpointer data) +{ + sync_pounces(); + save_timer = 0; + return FALSE; +} + +static void +schedule_pounces_save(void) +{ + if (save_timer == 0) + save_timer = gaim_timeout_add(5000, save_cb, NULL); +} + + +/********************************************************************* + * Reading from disk * + *********************************************************************/ + +static void +free_parser_data(gpointer user_data) +{ + PounceParserData *data = user_data; + + if (data->buffer != NULL) + g_string_free(data->buffer, TRUE); + + g_free(data->ui_name); + g_free(data->pouncee); + g_free(data->protocol_id); + g_free(data->event_type); + g_free(data->option_type); + g_free(data->action_name); + g_free(data->param_name); + g_free(data->account_name); + + g_free(data); +} + +static void +start_element_handler(GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + gpointer user_data, GError **error) +{ + PounceParserData *data = user_data; + GHashTable *atts; + int i; + + atts = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + + for (i = 0; attribute_names[i] != NULL; i++) { + g_hash_table_insert(atts, g_strdup(attribute_names[i]), + g_strdup(attribute_values[i])); + } + + if (data->buffer != NULL) { + g_string_free(data->buffer, TRUE); + data->buffer = NULL; + } + + if (!strcmp(element_name, "pounce")) { + const char *ui = g_hash_table_lookup(atts, "ui"); + + if (ui == NULL) { + gaim_debug(GAIM_DEBUG_ERROR, "pounce", + "Unset 'ui' parameter for pounce!\n"); + } + else + data->ui_name = g_strdup(ui); + + data->events = 0; + } + else if (!strcmp(element_name, "account")) { + const char *protocol_id = g_hash_table_lookup(atts, "protocol"); + + if (protocol_id == NULL) { + gaim_debug(GAIM_DEBUG_ERROR, "pounce", + "Unset 'protocol' parameter for account!\n"); + } + else + data->protocol_id = g_strdup(protocol_id); + } + else if (!strcmp(element_name, "option")) { + const char *type = g_hash_table_lookup(atts, "type"); + + if (type == NULL) { + gaim_debug(GAIM_DEBUG_ERROR, "pounce", + "Unset 'type' parameter for option!\n"); + } + else + data->option_type = g_strdup(type); + } + else if (!strcmp(element_name, "event")) { + const char *type = g_hash_table_lookup(atts, "type"); + + if (type == NULL) { + gaim_debug(GAIM_DEBUG_ERROR, "pounce", + "Unset 'type' parameter for event!\n"); + } + else + data->event_type = g_strdup(type); + } + else if (!strcmp(element_name, "action")) { + const char *type = g_hash_table_lookup(atts, "type"); + + if (type == NULL) { + gaim_debug(GAIM_DEBUG_ERROR, "pounce", + "Unset 'type' parameter for action!\n"); + } + else + data->action_name = g_strdup(type); + } + else if (!strcmp(element_name, "param")) { + const char *param_name = g_hash_table_lookup(atts, "name"); + + if (param_name == NULL) { + gaim_debug(GAIM_DEBUG_ERROR, "pounce", + "Unset 'name' parameter for param!\n"); + } + else + data->param_name = g_strdup(param_name); + } + + g_hash_table_destroy(atts); +} + +static void +end_element_handler(GMarkupParseContext *context, const gchar *element_name, + gpointer user_data, GError **error) +{ + PounceParserData *data = user_data; + gchar *buffer = NULL; + + if (data->buffer != NULL) { + buffer = g_string_free(data->buffer, FALSE); + data->buffer = NULL; + } + + if (!strcmp(element_name, "account")) { + g_free(data->account_name); + data->account_name = g_strdup(buffer); + } + else if (!strcmp(element_name, "pouncee")) { + g_free(data->pouncee); + data->pouncee = g_strdup(buffer); + } + else if (!strcmp(element_name, "option")) { + if (!strcmp(data->option_type, "on-away")) + data->options |= GAIM_POUNCE_OPTION_AWAY; + + g_free(data->option_type); + data->option_type = NULL; + } + else if (!strcmp(element_name, "event")) { + if (!strcmp(data->event_type, "sign-on")) + data->events |= GAIM_POUNCE_SIGNON; + else if (!strcmp(data->event_type, "sign-off")) + data->events |= GAIM_POUNCE_SIGNOFF; + else if (!strcmp(data->event_type, "away")) + data->events |= GAIM_POUNCE_AWAY; + else if (!strcmp(data->event_type, "return-from-away")) + data->events |= GAIM_POUNCE_AWAY_RETURN; + else if (!strcmp(data->event_type, "idle")) + data->events |= GAIM_POUNCE_IDLE; + else if (!strcmp(data->event_type, "return-from-idle")) + data->events |= GAIM_POUNCE_IDLE_RETURN; + else if (!strcmp(data->event_type, "start-typing")) + data->events |= GAIM_POUNCE_TYPING; + else if (!strcmp(data->event_type, "typed")) + data->events |= GAIM_POUNCE_TYPED; + else if (!strcmp(data->event_type, "stop-typing")) + data->events |= GAIM_POUNCE_TYPING_STOPPED; + else if (!strcmp(data->event_type, "message-received")) + data->events |= GAIM_POUNCE_MESSAGE_RECEIVED; + + g_free(data->event_type); + data->event_type = NULL; + } + else if (!strcmp(element_name, "action")) { + if (data->pounce != NULL) { + gaim_pounce_action_register(data->pounce, data->action_name); + gaim_pounce_action_set_enabled(data->pounce, data->action_name, TRUE); + } + + g_free(data->action_name); + data->action_name = NULL; + } + else if (!strcmp(element_name, "param")) { + if (data->pounce != NULL) { + gaim_pounce_action_set_attribute(data->pounce, data->action_name, + data->param_name, buffer); + } + + g_free(data->param_name); + data->param_name = NULL; + } + else if (!strcmp(element_name, "events")) { + GaimAccount *account; + + account = gaim_accounts_find(data->account_name, data->protocol_id); + + g_free(data->account_name); + g_free(data->protocol_id); + + data->account_name = NULL; + data->protocol_id = NULL; + + if (account == NULL) { + gaim_debug(GAIM_DEBUG_ERROR, "pounce", + "Account for pounce not found!\n"); + /* + * This pounce has effectively been removed, so make + * sure that we save the changes to pounces.xml + */ + schedule_pounces_save(); + } + else { + gaim_debug(GAIM_DEBUG_INFO, "pounce", + "Creating pounce: %s, %s\n", data->ui_name, + data->pouncee); + + data->pounce = gaim_pounce_new(data->ui_name, account, + data->pouncee, data->events, + data->options); + } + + g_free(data->pouncee); + data->pouncee = NULL; + } + else if (!strcmp(element_name, "save")) { + if (data->pounce != NULL) + gaim_pounce_set_save(data->pounce, TRUE); + } + else if (!strcmp(element_name, "pounce")) { + data->pounce = NULL; + data->events = 0; + data->options = 0; + + g_free(data->ui_name); + g_free(data->pouncee); + g_free(data->protocol_id); + g_free(data->event_type); + g_free(data->option_type); + g_free(data->action_name); + g_free(data->param_name); + g_free(data->account_name); + + data->ui_name = NULL; + data->pounce = NULL; + data->protocol_id = NULL; + data->event_type = NULL; + data->option_type = NULL; + data->action_name = NULL; + data->param_name = NULL; + data->account_name = NULL; + } + + g_free(buffer); +} + +static void +text_handler(GMarkupParseContext *context, const gchar *text, + gsize text_len, gpointer user_data, GError **error) +{ + PounceParserData *data = user_data; + + if (data->buffer == NULL) + data->buffer = g_string_new_len(text, text_len); + else + g_string_append_len(data->buffer, text, text_len); +} + +static GMarkupParser pounces_parser = +{ + start_element_handler, + end_element_handler, + text_handler, + NULL, + NULL +}; + +gboolean +gaim_pounces_load(void) +{ + gchar *filename = g_build_filename(gaim_user_dir(), "pounces.xml", NULL); + gchar *contents = NULL; + gsize length; + GMarkupParseContext *context; + GError *error = NULL; + PounceParserData *parser_data; + + if (filename == NULL) { + pounces_loaded = TRUE; + return FALSE; + } + + if (!g_file_get_contents(filename, &contents, &length, &error)) { + gaim_debug(GAIM_DEBUG_ERROR, "pounce", + "Error reading pounces: %s\n", error->message); + + g_free(filename); + g_error_free(error); + + pounces_loaded = TRUE; + return FALSE; + } + + parser_data = g_new0(PounceParserData, 1); + + context = g_markup_parse_context_new(&pounces_parser, 0, + parser_data, free_parser_data); + + if (!g_markup_parse_context_parse(context, contents, length, NULL)) { + g_markup_parse_context_free(context); + g_free(contents); + g_free(filename); + + pounces_loaded = TRUE; + + return FALSE; + } + + if (!g_markup_parse_context_end_parse(context, NULL)) { + gaim_debug(GAIM_DEBUG_ERROR, "pounce", "Error parsing %s\n", + filename); + + g_markup_parse_context_free(context); + g_free(contents); + g_free(filename); + pounces_loaded = TRUE; + + return FALSE; + } + + g_markup_parse_context_free(context); + g_free(contents); + g_free(filename); + + pounces_loaded = TRUE; + + return TRUE; +} + + +GaimPounce * +gaim_pounce_new(const char *ui_type, GaimAccount *pouncer, + const char *pouncee, GaimPounceEvent event, + GaimPounceOption option) +{ + GaimPounce *pounce; + GaimPounceHandler *handler; + + g_return_val_if_fail(ui_type != NULL, NULL); + g_return_val_if_fail(pouncer != NULL, NULL); + g_return_val_if_fail(pouncee != NULL, NULL); + g_return_val_if_fail(event != 0, NULL); + + pounce = g_new0(GaimPounce, 1); + + pounce->ui_type = g_strdup(ui_type); + pounce->pouncer = pouncer; + pounce->pouncee = g_strdup(pouncee); + pounce->events = event; + pounce->options = option; + + pounce->actions = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, free_action_data); + + handler = g_hash_table_lookup(pounce_handlers, pounce->ui_type); + + if (handler != NULL && handler->new_pounce != NULL) + handler->new_pounce(pounce); + + pounces = g_list_append(pounces, pounce); + + schedule_pounces_save(); + + return pounce; +} + +void +gaim_pounce_destroy(GaimPounce *pounce) +{ + GaimPounceHandler *handler; + + g_return_if_fail(pounce != NULL); + + handler = g_hash_table_lookup(pounce_handlers, pounce->ui_type); + + pounces = g_list_remove(pounces, pounce); + + g_free(pounce->ui_type); + g_free(pounce->pouncee); + + g_hash_table_destroy(pounce->actions); + + if (handler != NULL && handler->free_pounce != NULL) + handler->free_pounce(pounce); + + g_free(pounce); + + schedule_pounces_save(); +} + +void +gaim_pounce_destroy_all_by_account(GaimAccount *account) +{ + GaimAccount *pouncer; + GaimPounce *pounce; + GList *l, *l_next; + + g_return_if_fail(account != NULL); + + for (l = gaim_pounces_get_all(); l != NULL; l = l_next) + { + pounce = (GaimPounce *)l->data; + l_next = l->next; + + pouncer = gaim_pounce_get_pouncer(pounce); + if (pouncer == account) + gaim_pounce_destroy(pounce); + } +} + +void +gaim_pounce_set_events(GaimPounce *pounce, GaimPounceEvent events) +{ + g_return_if_fail(pounce != NULL); + g_return_if_fail(events != GAIM_POUNCE_NONE); + + pounce->events = events; + + schedule_pounces_save(); +} + +void +gaim_pounce_set_options(GaimPounce *pounce, GaimPounceOption options) +{ + g_return_if_fail(pounce != NULL); + + pounce->options = options; + + schedule_pounces_save(); +} + +void +gaim_pounce_set_pouncer(GaimPounce *pounce, GaimAccount *pouncer) +{ + g_return_if_fail(pounce != NULL); + g_return_if_fail(pouncer != NULL); + + pounce->pouncer = pouncer; + + schedule_pounces_save(); +} + +void +gaim_pounce_set_pouncee(GaimPounce *pounce, const char *pouncee) +{ + g_return_if_fail(pounce != NULL); + g_return_if_fail(pouncee != NULL); + + g_free(pounce->pouncee); + pounce->pouncee = g_strdup(pouncee); + + schedule_pounces_save(); +} + +void +gaim_pounce_set_save(GaimPounce *pounce, gboolean save) +{ + g_return_if_fail(pounce != NULL); + + pounce->save = save; + + schedule_pounces_save(); +} + +void +gaim_pounce_action_register(GaimPounce *pounce, const char *name) +{ + GaimPounceActionData *action_data; + + g_return_if_fail(pounce != NULL); + g_return_if_fail(name != NULL); + + if (g_hash_table_lookup(pounce->actions, name) != NULL) + return; + + action_data = g_new0(GaimPounceActionData, 1); + + action_data->name = g_strdup(name); + action_data->enabled = FALSE; + action_data->atts = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, g_free); + + g_hash_table_insert(pounce->actions, g_strdup(name), action_data); + + schedule_pounces_save(); +} + +void +gaim_pounce_action_set_enabled(GaimPounce *pounce, const char *action, + gboolean enabled) +{ + GaimPounceActionData *action_data; + + g_return_if_fail(pounce != NULL); + g_return_if_fail(action != NULL); + + action_data = find_action_data(pounce, action); + + g_return_if_fail(action_data != NULL); + + action_data->enabled = enabled; + + schedule_pounces_save(); +} + +void +gaim_pounce_action_set_attribute(GaimPounce *pounce, const char *action, + const char *attr, const char *value) +{ + GaimPounceActionData *action_data; + + g_return_if_fail(pounce != NULL); + g_return_if_fail(action != NULL); + g_return_if_fail(attr != NULL); + + action_data = find_action_data(pounce, action); + + g_return_if_fail(action_data != NULL); + + if (value == NULL) + g_hash_table_remove(action_data->atts, attr); + else + g_hash_table_insert(action_data->atts, g_strdup(attr), + g_strdup(value)); + + schedule_pounces_save(); +} + +void +gaim_pounce_set_data(GaimPounce *pounce, void *data) +{ + g_return_if_fail(pounce != NULL); + + pounce->data = data; + + schedule_pounces_save(); +} + +GaimPounceEvent +gaim_pounce_get_events(const GaimPounce *pounce) +{ + g_return_val_if_fail(pounce != NULL, GAIM_POUNCE_NONE); + + return pounce->events; +} + +GaimPounceOption +gaim_pounce_get_options(const GaimPounce *pounce) +{ + g_return_val_if_fail(pounce != NULL, GAIM_POUNCE_OPTION_NONE); + + return pounce->options; +} + +GaimAccount * +gaim_pounce_get_pouncer(const GaimPounce *pounce) +{ + g_return_val_if_fail(pounce != NULL, NULL); + + return pounce->pouncer; +} + +const char * +gaim_pounce_get_pouncee(const GaimPounce *pounce) +{ + g_return_val_if_fail(pounce != NULL, NULL); + + return pounce->pouncee; +} + +gboolean +gaim_pounce_get_save(const GaimPounce *pounce) +{ + g_return_val_if_fail(pounce != NULL, FALSE); + + return pounce->save; +} + +gboolean +gaim_pounce_action_is_enabled(const GaimPounce *pounce, const char *action) +{ + GaimPounceActionData *action_data; + + g_return_val_if_fail(pounce != NULL, FALSE); + g_return_val_if_fail(action != NULL, FALSE); + + action_data = find_action_data(pounce, action); + + g_return_val_if_fail(action_data != NULL, FALSE); + + return action_data->enabled; +} + +const char * +gaim_pounce_action_get_attribute(const GaimPounce *pounce, + const char *action, const char *attr) +{ + GaimPounceActionData *action_data; + + g_return_val_if_fail(pounce != NULL, NULL); + g_return_val_if_fail(action != NULL, NULL); + g_return_val_if_fail(attr != NULL, NULL); + + action_data = find_action_data(pounce, action); + + g_return_val_if_fail(action_data != NULL, NULL); + + return g_hash_table_lookup(action_data->atts, attr); +} + +void * +gaim_pounce_get_data(const GaimPounce *pounce) +{ + g_return_val_if_fail(pounce != NULL, NULL); + + return pounce->data; +} + +void +gaim_pounce_execute(const GaimAccount *pouncer, const char *pouncee, + GaimPounceEvent events) +{ + GaimPounce *pounce; + GaimPounceHandler *handler; + GaimPresence *presence; + GList *l, *l_next; + char *norm_pouncee; + + g_return_if_fail(pouncer != NULL); + g_return_if_fail(pouncee != NULL); + g_return_if_fail(events != GAIM_POUNCE_NONE); + + norm_pouncee = g_strdup(gaim_normalize(pouncer, pouncee)); + + for (l = gaim_pounces_get_all(); l != NULL; l = l_next) + { + pounce = (GaimPounce *)l->data; + l_next = l->next; + + presence = gaim_account_get_presence(pouncer); + + if ((gaim_pounce_get_events(pounce) & events) && + (gaim_pounce_get_pouncer(pounce) == pouncer) && + !gaim_utf8_strcasecmp(gaim_normalize(pouncer, gaim_pounce_get_pouncee(pounce)), + norm_pouncee) && + (pounce->options == GAIM_POUNCE_OPTION_NONE || + (pounce->options & GAIM_POUNCE_OPTION_AWAY && + !gaim_presence_is_available(presence)))) + { + handler = g_hash_table_lookup(pounce_handlers, pounce->ui_type); + + if (handler != NULL && handler->cb != NULL) + { + handler->cb(pounce, events, gaim_pounce_get_data(pounce)); + + if (!gaim_pounce_get_save(pounce)) + gaim_pounce_destroy(pounce); + } + } + } + + g_free(norm_pouncee); +} + +GaimPounce * +gaim_find_pounce(const GaimAccount *pouncer, const char *pouncee, + GaimPounceEvent events) +{ + GaimPounce *pounce = NULL; + GList *l; + char *norm_pouncee; + + g_return_val_if_fail(pouncer != NULL, NULL); + g_return_val_if_fail(pouncee != NULL, NULL); + g_return_val_if_fail(events != GAIM_POUNCE_NONE, NULL); + + norm_pouncee = g_strdup(gaim_normalize(pouncer, pouncee)); + + for (l = gaim_pounces_get_all(); l != NULL; l = l->next) + { + pounce = (GaimPounce *)l->data; + + if ((gaim_pounce_get_events(pounce) & events) && + (gaim_pounce_get_pouncer(pounce) == pouncer) && + !gaim_utf8_strcasecmp(gaim_normalize(pouncer, gaim_pounce_get_pouncee(pounce)), + norm_pouncee)) + { + break; + } + + pounce = NULL; + } + + g_free(norm_pouncee); + + return pounce; +} + +void +gaim_pounces_register_handler(const char *ui, GaimPounceCb cb, + void (*new_pounce)(GaimPounce *pounce), + void (*free_pounce)(GaimPounce *pounce)) +{ + GaimPounceHandler *handler; + + g_return_if_fail(ui != NULL); + g_return_if_fail(cb != NULL); + + handler = g_new0(GaimPounceHandler, 1); + + handler->ui = g_strdup(ui); + handler->cb = cb; + handler->new_pounce = new_pounce; + handler->free_pounce = free_pounce; + + g_hash_table_insert(pounce_handlers, g_strdup(ui), handler); +} + +void +gaim_pounces_unregister_handler(const char *ui) +{ + g_return_if_fail(ui != NULL); + + g_hash_table_remove(pounce_handlers, ui); +} + +GList * +gaim_pounces_get_all(void) +{ + return pounces; +} + +static void +free_pounce_handler(gpointer user_data) +{ + GaimPounceHandler *handler = (GaimPounceHandler *)user_data; + + g_free(handler->ui); + g_free(handler); +} + +static void +buddy_state_cb(GaimBuddy *buddy, GaimPounceEvent event) +{ + gaim_pounce_execute(buddy->account, buddy->name, event); +} + +static void +buddy_status_changed_cb(GaimBuddy *buddy, GaimStatus *old_status, + GaimStatus *status) +{ + gboolean old_available, available; + + available = gaim_status_is_available(status); + old_available = gaim_status_is_available(old_status); + + if (available && !old_available) + gaim_pounce_execute(buddy->account, buddy->name, + GAIM_POUNCE_AWAY_RETURN); + else if (!available && old_available) + gaim_pounce_execute(buddy->account, buddy->name, + GAIM_POUNCE_AWAY); +} + +static void +buddy_idle_changed_cb(GaimBuddy *buddy, gboolean old_idle, gboolean idle) +{ + if (idle && !old_idle) + gaim_pounce_execute(buddy->account, buddy->name, + GAIM_POUNCE_IDLE); + else if (!idle && old_idle) + gaim_pounce_execute(buddy->account, buddy->name, + GAIM_POUNCE_IDLE_RETURN); +} + +static void +buddy_typing_cb(GaimAccount *account, const char *name, void *data) +{ + GaimConversation *conv; + + conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM, name, account); + if (conv != NULL) + { + GaimTypingState state; + GaimPounceEvent event; + + state = gaim_conv_im_get_typing_state(GAIM_CONV_IM(conv)); + if (state == GAIM_TYPED) + event = GAIM_POUNCE_TYPED; + else if (state == GAIM_NOT_TYPING) + event = GAIM_POUNCE_TYPING_STOPPED; + else + event = GAIM_POUNCE_TYPING; + + gaim_pounce_execute(account, name, event); + } +} + +static void +received_message_cb(GaimAccount *account, const char *name, void *data) +{ + gaim_pounce_execute(account, name, GAIM_POUNCE_MESSAGE_RECEIVED); +} + +void * +gaim_pounces_get_handle(void) +{ + static int pounce_handle; + + return &pounce_handle; +} + +void +gaim_pounces_init(void) +{ + void *handle = gaim_pounces_get_handle(); + void *blist_handle = gaim_blist_get_handle(); + void *conv_handle = gaim_conversations_get_handle(); + + pounce_handlers = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, free_pounce_handler); + + gaim_signal_connect(blist_handle, "buddy-idle-changed", + handle, GAIM_CALLBACK(buddy_idle_changed_cb), NULL); + gaim_signal_connect(blist_handle, "buddy-status-changed", + handle, GAIM_CALLBACK(buddy_status_changed_cb), NULL); + gaim_signal_connect(blist_handle, "buddy-signed-on", + handle, GAIM_CALLBACK(buddy_state_cb), + GINT_TO_POINTER(GAIM_POUNCE_SIGNON)); + gaim_signal_connect(blist_handle, "buddy-signed-off", + handle, GAIM_CALLBACK(buddy_state_cb), + GINT_TO_POINTER(GAIM_POUNCE_SIGNOFF)); + + gaim_signal_connect(conv_handle, "buddy-typing", + handle, GAIM_CALLBACK(buddy_typing_cb), NULL); + gaim_signal_connect(conv_handle, "buddy-typed", + handle, GAIM_CALLBACK(buddy_typing_cb), NULL); + gaim_signal_connect(conv_handle, "buddy-typing-stopped", + handle, GAIM_CALLBACK(buddy_typing_cb), NULL); + + gaim_signal_connect(conv_handle, "received-im-msg", + handle, GAIM_CALLBACK(received_message_cb), NULL); +} + +void +gaim_pounces_uninit() +{ + if (save_timer != 0) + { + gaim_timeout_remove(save_timer); + save_timer = 0; + sync_pounces(); + } + + gaim_signals_disconnect_by_handle(gaim_pounces_get_handle()); +} diff -r d10dda2777a9 -r b63ebf84c42b core/pounce.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/pounce.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,368 @@ +/** + * @file pounce.h Buddy Pounce API + * @ingroup core + * + * 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 + */ +#ifndef _GAIM_POUNCE_H_ +#define _GAIM_POUNCE_H_ + +typedef struct _GaimPounce GaimPounce; + +#include +#include "account.h" + +/** + * Events that trigger buddy pounces. + */ +typedef enum +{ + GAIM_POUNCE_NONE = 0x000, /**< No events. */ + GAIM_POUNCE_SIGNON = 0x001, /**< The buddy signed on. */ + GAIM_POUNCE_SIGNOFF = 0x002, /**< The buddy signed off. */ + GAIM_POUNCE_AWAY = 0x004, /**< The buddy went away. */ + GAIM_POUNCE_AWAY_RETURN = 0x008, /**< The buddy returned from away. */ + GAIM_POUNCE_IDLE = 0x010, /**< The buddy became idle. */ + GAIM_POUNCE_IDLE_RETURN = 0x020, /**< The buddy is no longer idle. */ + GAIM_POUNCE_TYPING = 0x040, /**< The buddy started typing. */ + GAIM_POUNCE_TYPED = 0x080, /**< The buddy has entered text. */ + GAIM_POUNCE_TYPING_STOPPED = 0x100, /**< The buddy stopped typing. */ + GAIM_POUNCE_MESSAGE_RECEIVED = 0x200 /**< The buddy sent a message */ + +} GaimPounceEvent; + +typedef enum +{ + GAIM_POUNCE_OPTION_NONE = 0x00, /**< No Option */ + GAIM_POUNCE_OPTION_AWAY = 0x01 /**< Pounce only when away */ +} GaimPounceOption; + +/** A pounce callback. */ +typedef void (*GaimPounceCb)(GaimPounce *, GaimPounceEvent, void *); + +/** + * A buddy pounce structure. + * + * Buddy pounces are actions triggered by a buddy-related event. For + * example, a sound can be played or an IM window opened when a buddy + * signs on or returns from away. Such responses are handled in the + * UI. The events themselves are done in the core. + */ +struct _GaimPounce +{ + char *ui_type; /**< The type of UI. */ + + GaimPounceEvent events; /**< The event(s) to pounce on. */ + GaimPounceOption options; /**< The pounce options */ + GaimAccount *pouncer; /**< The user who is pouncing. */ + + char *pouncee; /**< The buddy to pounce on. */ + + GHashTable *actions; /**< The registered actions. */ + + gboolean save; /**< Whether or not the pounce should + be saved after activation. */ + void *data; /**< Pounce-specific data. */ +}; + +#ifdef __cplusplus +extern "C" { +#endif + +/**************************************************************************/ +/** @name Buddy Pounce API */ +/**************************************************************************/ +/*@{*/ + +/** + * Creates a new buddy pounce. + * + * @param ui_type The type of UI the pounce is for. + * @param pouncer The account that will pounce. + * @param pouncee The buddy to pounce on. + * @param event The event(s) to pounce on. + * @param option Pounce options. + * + * @return The new buddy pounce structure. + */ +GaimPounce *gaim_pounce_new(const char *ui_type, GaimAccount *pouncer, + const char *pouncee, GaimPounceEvent event, + GaimPounceOption option); + +/** + * Destroys a buddy pounce. + * + * @param pounce The buddy pounce. + */ +void gaim_pounce_destroy(GaimPounce *pounce); + +/** + * Destroys all buddy pounces for the account + * + * @param account The account to remove all pounces from. + */ +void gaim_pounce_destroy_all_by_account(GaimAccount *account); + +/** + * Sets the events a pounce should watch for. + * + * @param pounce The buddy pounce. + * @param events The events to watch for. + */ +void gaim_pounce_set_events(GaimPounce *pounce, GaimPounceEvent events); + +/** + * Sets the options for a pounce. + * + * @param pounce The buddy pounce. + * @param options The options for the pounce. + */ +void gaim_pounce_set_options(GaimPounce *pounce, GaimPounceOption options); + +/** + * Sets the account that will do the pouncing. + * + * @param pounce The buddy pounce. + * @param pouncer The account that will pounce. + */ +void gaim_pounce_set_pouncer(GaimPounce *pounce, GaimAccount *pouncer); + +/** + * Sets the buddy a pounce should pounce on. + * + * @param pounce The buddy pounce. + * @param pouncee The buddy to pounce on. + */ +void gaim_pounce_set_pouncee(GaimPounce *pounce, const char *pouncee); + +/** + * Sets whether or not the pounce should be saved after execution. + * + * @param pounce The buddy pounce. + * @param save @c TRUE if the pounce should be saved, or @c FALSE otherwise. + */ +void gaim_pounce_set_save(GaimPounce *pounce, gboolean save); + +/** + * Registers an action type for the pounce. + * + * @param pounce The buddy pounce. + * @param name The action name. + */ +void gaim_pounce_action_register(GaimPounce *pounce, const char *name); + +/** + * Enables or disables an action for a pounce. + * + * @param pounce The buddy pounce. + * @param action The name of the action. + * @param enabled The enabled state. + */ +void gaim_pounce_action_set_enabled(GaimPounce *pounce, const char *action, + gboolean enabled); + +/** + * Sets a value for an attribute in an action. + * + * If @a value is @c NULL, the value will be unset. + * + * @param pounce The buddy pounce. + * @param action The action name. + * @param attr The attribute name. + * @param value The value. + */ +void gaim_pounce_action_set_attribute(GaimPounce *pounce, const char *action, + const char *attr, const char *value); + +/** + * Sets the pounce-specific data. + * + * @param pounce The buddy pounce. + * @param data Data specific to the pounce. + */ +void gaim_pounce_set_data(GaimPounce *pounce, void *data); + +/** + * Returns the events a pounce should watch for. + * + * @param pounce The buddy pounce. + * + * @return The events the pounce is watching for. + */ +GaimPounceEvent gaim_pounce_get_events(const GaimPounce *pounce); + +/** + * Returns the options for a pounce. + * + * @param pounce The buddy pounce. + * + * @return The options for the pounce. + */ +GaimPounceOption gaim_pounce_get_options(const GaimPounce *pounce); + +/** + * Returns the account that will do the pouncing. + * + * @param pounce The buddy pounce. + * + * @return The account that will pounce. + */ +GaimAccount *gaim_pounce_get_pouncer(const GaimPounce *pounce); + +/** + * Returns the buddy a pounce should pounce on. + * + * @param pounce The buddy pounce. + * + * @return The buddy to pounce on. + */ +const char *gaim_pounce_get_pouncee(const GaimPounce *pounce); + +/** + * Returns whether or not the pounce should save after execution. + * + * @param pounce The buddy pounce. + * + * @return @c TRUE if the pounce should be saved after execution, or + * @c FALSE otherwise. + */ +gboolean gaim_pounce_get_save(const GaimPounce *pounce); + +/** + * Returns whether or not an action is enabled. + * + * @param pounce The buddy pounce. + * @param action The action name. + * + * @return @c TRUE if the action is enabled, or @c FALSE otherwise. + */ +gboolean gaim_pounce_action_is_enabled(const GaimPounce *pounce, + const char *action); + +/** + * Returns the value for an attribute in an action. + * + * @param pounce The buddy pounce. + * @param action The action name. + * @param attr The attribute name. + * + * @return The attribute value, if it exists, or @c NULL. + */ +const char *gaim_pounce_action_get_attribute(const GaimPounce *pounce, + const char *action, + const char *attr); + +/** + * Returns the pounce-specific data. + * + * @param pounce The buddy pounce. + * + * @return The data specific to a buddy pounce. + */ +void *gaim_pounce_get_data(const GaimPounce *pounce); + +/** + * Executes a pounce with the specified pouncer, pouncee, and event type. + * + * @param pouncer The account that will do the pouncing. + * @param pouncee The buddy that is being pounced. + * @param events The events that triggered the pounce. + */ +void gaim_pounce_execute(const GaimAccount *pouncer, const char *pouncee, + GaimPounceEvent events); + +/*@}*/ + +/**************************************************************************/ +/** @name Buddy Pounce Subsystem API */ +/**************************************************************************/ +/*@{*/ + +/** + * Finds a pounce with the specified event(s) and buddy. + * + * @param pouncer The account to match against. + * @param pouncee The buddy to match against. + * @param events The event(s) to match against. + * + * @return The pounce if found, or @c NULL otherwise. + */ +GaimPounce *gaim_find_pounce(const GaimAccount *pouncer, + const char *pouncee, GaimPounceEvent events); + + +/** + * Loads the pounces. + * + * @return @c TRUE if the pounces could be loaded. + */ +gboolean gaim_pounces_load(void); + +/** + * Registers a pounce handler for a UI. + * + * @param ui The UI name. + * @param cb The callback function. + * @param new_pounce The function called when a pounce is created. + * @param free_pounce The function called when a pounce is freed. + */ +void gaim_pounces_register_handler(const char *ui, GaimPounceCb cb, + void (*new_pounce)(GaimPounce *pounce), + void (*free_pounce)(GaimPounce *pounce)); + +/** + * Unregisters a pounce handle for a UI. + * + * @param ui The UI name. + */ +void gaim_pounces_unregister_handler(const char *ui); + +/** + * Returns a list of all registered buddy pounces. + * + * @return The list of buddy pounces. + */ +GList *gaim_pounces_get_all(void); + +/** + * Returns the buddy pounce subsystem handle. + * + * @return The subsystem handle. + */ +void *gaim_pounces_get_handle(void); + +/** + * Initializes the pounces subsystem. + */ +void gaim_pounces_init(void); + +/** + * Uninitializes the pounces subsystem. + */ +void gaim_pounces_uninit(void); + +/*@}*/ + +#ifdef __cplusplus +} +#endif + +#endif /* _GAIM_POUNCE_H_ */ diff -r d10dda2777a9 -r b63ebf84c42b core/prefix.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/prefix.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,476 @@ +/* + * BinReloc - a library for creating relocatable executables + * Written by: Mike Hearn + * Hongli Lai + * http://autopackage.org/ + * + * This source code is public domain. You can relicense this code + * under whatever license you want. + * + * NOTE: if you're using C++ and are getting "undefined reference + * to br_*", try renaming prefix.c to prefix.cpp + */ + +/* WARNING, BEFORE YOU MODIFY PREFIX.C: + * + * If you make changes to any of the functions in prefix.c, you MUST + * change the BR_NAMESPACE macro (in prefix.h). + * This way you can avoid symbol table conflicts with other libraries + * that also happen to use BinReloc. + * + * Example: + * #define BR_NAMESPACE(funcName) foobar_ ## funcName + * --> expands br_locate to foobar_br_locate + */ + +#ifndef _PREFIX_C_ +#define _PREFIX_C_ + +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif + +#ifndef BR_PTHREADS + /* Change 1 to 0 if you don't want pthread support */ + #define BR_PTHREADS 1 +#endif /* BR_PTHREADS */ + +#include +#include +#include +#include +#include "prefix.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +#undef NULL +#define NULL ((void *) 0) + +#ifdef __GNUC__ + #define br_return_val_if_fail(expr,val) if (!(expr)) {fprintf (stderr, "** BinReloc (%s): assertion %s failed\n", __PRETTY_FUNCTION__, #expr); return val;} +#else + #define br_return_val_if_fail(expr,val) if (!(expr)) return val +#endif /* __GNUC__ */ + + +static br_locate_fallback_func fallback_func = NULL; +static void *fallback_data = NULL; + + +#ifdef ENABLE_BINRELOC +#include +#include +#include +#include + + +/** + * br_locate: + * symbol: A symbol that belongs to the app/library you want to locate. + * Returns: A newly allocated string containing the full path of the + * app/library that func belongs to, or NULL on error. This + * string should be freed when not when no longer needed. + * + * Finds out to which application or library symbol belongs, then locate + * the full path of that application or library. + * Note that symbol cannot be a pointer to a function. That will not work. + * + * Example: + * --> main.c + * #include "prefix.h" + * #include "libfoo.h" + * + * int main (int argc, char *argv[]) { + * printf ("Full path of this app: %s\n", br_locate (&argc)); + * libfoo_start (); + * return 0; + * } + * + * --> libfoo.c starts here + * #include "prefix.h" + * + * void libfoo_start () { + * --> "" is a symbol that belongs to libfoo (because it's called + * --> from libfoo_start()); that's why this works. + * printf ("libfoo is located in: %s\n", br_locate ("")); + * } + */ +char * +br_locate (void *symbol) +{ + char line[5000]; + FILE *f; + char *path; + + br_return_val_if_fail (symbol != NULL, NULL); + + f = fopen ("/proc/self/maps", "r"); + if (!f) { + if (fallback_func) + return fallback_func(symbol, fallback_data); + else + return NULL; + } + + while (!feof (f)) + { + unsigned long start, end; + + if (!fgets (line, sizeof (line), f)) + continue; + if (!strstr (line, " r-xp ") || !strchr (line, '/')) + continue; + + sscanf (line, "%lx-%lx ", &start, &end); + if (symbol >= (void *) start && symbol < (void *) end) + { + char *tmp; + size_t len; + + /* Extract the filename; it is always an absolute path */ + path = strchr (line, '/'); + + /* Get rid of the newline */ + tmp = strrchr (path, '\n'); + if (tmp) *tmp = 0; + + /* Get rid of "(deleted)" */ + len = strlen (path); + if (len > 10 && strcmp (path + len - 10, " (deleted)") == 0) + { + tmp = path + len - 10; + *tmp = 0; + } + + fclose(f); + return strdup (path); + } + } + + fclose (f); + return NULL; +} + + +/** + * br_locate_prefix: + * symbol: A symbol that belongs to the app/library you want to locate. + * Returns: A prefix. This string should be freed when no longer needed. + * + * Locates the full path of the app/library that symbol belongs to, and return + * the prefix of that path, or NULL on error. + * Note that symbol cannot be a pointer to a function. That will not work. + * + * Example: + * --> This application is located in /usr/bin/foo + * br_locate_prefix (&argc); --> returns: "/usr" + */ +char * +br_locate_prefix (void *symbol) +{ + char *path, *prefix; + + br_return_val_if_fail (symbol != NULL, NULL); + + path = br_locate (symbol); + if (!path) return NULL; + + prefix = br_extract_prefix (path); + free (path); + return prefix; +} + + +/** + * br_prepend_prefix: + * symbol: A symbol that belongs to the app/library you want to locate. + * path: The path that you want to prepend the prefix to. + * Returns: The new path, or NULL on error. This string should be freed when no + * longer needed. + * + * Gets the prefix of the app/library that symbol belongs to. Prepend that prefix to path. + * Note that symbol cannot be a pointer to a function. That will not work. + * + * Example: + * --> The application is /usr/bin/foo + * br_prepend_prefix (&argc, "/share/foo/data.png"); --> Returns "/usr/share/foo/data.png" + */ +char * +br_prepend_prefix (void *symbol, char *path) +{ + char *tmp, *newpath; + + br_return_val_if_fail (symbol != NULL, NULL); + br_return_val_if_fail (path != NULL, NULL); + + tmp = br_locate_prefix (symbol); + if (!tmp) return NULL; + + if (strcmp (tmp, "/") == 0) + newpath = strdup (path); + else + newpath = br_strcat (tmp, path); + + /* Get rid of compiler warning ("br_prepend_prefix never used") */ + if (0) br_prepend_prefix (NULL, NULL); + + free (tmp); + return newpath; +} + +#endif /* ENABLE_BINRELOC */ + + +/* Pthread stuff for thread safetiness */ +#if BR_PTHREADS + +#include + +static pthread_key_t br_thread_key; +static pthread_once_t br_thread_key_once = PTHREAD_ONCE_INIT; + + +static void +br_thread_local_store_fini () +{ + char *specific; + + specific = (char *) pthread_getspecific (br_thread_key); + if (specific) + { + free (specific); + pthread_setspecific (br_thread_key, NULL); + } + pthread_key_delete (br_thread_key); + br_thread_key = 0; +} + + +static void +br_str_free (void *str) +{ + if (str) + free (str); +} + + +static void +br_thread_local_store_init () +{ + if (pthread_key_create (&br_thread_key, br_str_free) == 0) + atexit (br_thread_local_store_fini); +} + +#else /* BR_PTHREADS */ + +static char *br_last_value = (char *) NULL; + +static void +br_free_last_value () +{ + if (br_last_value) + free (br_last_value); +} + +#endif /* BR_PTHREADS */ + + +/** + * br_thread_local_store: + * str: A dynamically allocated string. + * Returns: str. This return value must not be freed. + * + * Store str in a thread-local variable and return str. The next + * you run this function, that variable is freed too. + * This function is created so you don't have to worry about freeing + * strings. Just be careful about doing this sort of thing: + * + * some_function( BR_DATADIR("/one.png"), BR_DATADIR("/two.png") ) + * + * Examples: + * char *foo; + * foo = br_thread_local_store (strdup ("hello")); --> foo == "hello" + * foo = br_thread_local_store (strdup ("world")); --> foo == "world"; "hello" is now freed. + */ +const char * +br_thread_local_store (char *str) +{ + #if BR_PTHREADS + char *specific; + + pthread_once (&br_thread_key_once, br_thread_local_store_init); + + specific = (char *) pthread_getspecific (br_thread_key); + br_str_free (specific); + pthread_setspecific (br_thread_key, str); + + #else /* BR_PTHREADS */ + static int initialized = 0; + + if (!initialized) + { + atexit (br_free_last_value); + initialized = 1; + } + + if (br_last_value) + free (br_last_value); + br_last_value = str; + #endif /* BR_PTHREADS */ + + return (const char *) str; +} + + +/** + * br_strcat: + * str1: A string. + * str2: Another string. + * Returns: A newly-allocated string. This string should be freed when no longer needed. + * + * Concatenate str1 and str2 to a newly allocated string. + */ +char * +br_strcat (const char *str1, const char *str2) +{ + char *result; + size_t len1, len2; + + if (!str1) str1 = ""; + if (!str2) str2 = ""; + + len1 = strlen (str1); + len2 = strlen (str2); + + result = (char *) malloc (len1 + len2 + 1); + memcpy (result, str1, len1); + memcpy (result + len1, str2, len2); + result[len1 + len2] = '\0'; + + return result; +} + + +/* Emulates glibc's strndup() */ +static char * +br_strndup (char *str, size_t size) +{ + char *result = (char *) NULL; + size_t len; + + br_return_val_if_fail (str != (char *) NULL, (char *) NULL); + + len = strlen (str); + if (!len) return strdup (""); + if (size > len) size = len; + + result = (char *) calloc (sizeof (char), len + 1); + memcpy (result, str, size); + return result; +} + + +/** + * br_extract_dir: + * path: A path. + * Returns: A directory name. This string should be freed when no longer needed. + * + * Extracts the directory component of path. Similar to g_dirname() or the dirname + * commandline application. + * + * Example: + * br_extract_dir ("/usr/local/foobar"); --> Returns: "/usr/local" + */ +char * +br_extract_dir (const char *path) +{ + char *end, *result; + + br_return_val_if_fail (path != (char *) NULL, (char *) NULL); + + end = strrchr (path, '/'); + if (!end) return strdup ("."); + + while (end > path && *end == '/') + end--; + result = br_strndup ((char *) path, end - path + 1); + if (!*result) + { + free (result); + return strdup ("/"); + } else + return result; +} + + +/** + * br_extract_prefix: + * path: The full path of an executable or library. + * Returns: The prefix, or NULL on error. This string should be freed when no longer needed. + * + * Extracts the prefix from path. This function assumes that your executable + * or library is installed in an LSB-compatible directory structure. + * + * Example: + * br_extract_prefix ("/usr/bin/gnome-panel"); --> Returns "/usr" + * br_extract_prefix ("/usr/local/lib/libfoo.so"); --> Returns "/usr/local" + * br_extract_prefix ("/usr/local/libfoo.so"); --> Returns "/usr" + */ +char * +br_extract_prefix (const char *path) +{ + char *end, *tmp, *result; + + br_return_val_if_fail (path != (char *) NULL, (char *) NULL); + + if (!*path) return strdup ("/"); + end = strrchr (path, '/'); + if (!end) return strdup (path); + + tmp = br_strndup ((char *) path, end - path); + if (!*tmp) + { + free (tmp); + return strdup ("/"); + } + end = strrchr (tmp, '/'); + if (!end) return tmp; + + result = br_strndup (tmp, end - tmp); + free (tmp); + + if (!*result) + { + free (result); + result = strdup ("/"); + } + + return result; +} + + +/** + * br_set_fallback_function: + * func: A function to call to find the binary. + * data: User data to pass to func. + * + * Sets a function to call to find the path to the binary, in + * case "/proc/self/maps" can't be opened. The function set should + * return a string that is safe to free with free(). + */ +void +br_set_locate_fallback_func (br_locate_fallback_func func, void *data) +{ + fallback_func = func; + fallback_data = data; +} + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* _PREFIX_C */ diff -r d10dda2777a9 -r b63ebf84c42b core/prefix.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/prefix.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,144 @@ +/* + * I made the following modifications, be sure to readd these when + * upgrading these files. + * + * Added this comment. + * Added "gaim_ ## " to the namespace + * Changed the lib macro to use /lib/gaim instead of just /lib + * (why does gaim do that in the -DLIBDIR autoconf thing anyway?) + * + */ + +/* + * BinReloc - a library for creating relocatable executables + * Written by: Mike Hearn + * Hongli Lai + * http://autopackage.org/ + * + * This source code is public domain. You can relicense this code + * under whatever license you want. + * + * See http://autopackage.org/docs/binreloc/ for + * more information and how to use this. + * + * NOTE: if you're using C++ and are getting "undefined reference + * to br_*", try renaming prefix.c to prefix.cpp + */ + +#ifndef _PREFIX_H_ +#define _PREFIX_H_ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* WARNING, BEFORE YOU MODIFY PREFIX.C: + * + * If you make changes to any of the functions in prefix.c, you MUST + * change the BR_NAMESPACE macro. + * This way you can avoid symbol table conflicts with other libraries + * that also happen to use BinReloc. + * + * Example: + * #define BR_NAMESPACE(funcName) foobar_ ## funcName + * --> expands br_locate to foobar_br_locate + */ +#undef BR_NAMESPACE +#define BR_NAMESPACE(funcName) gaim_ ## funcName + + +#ifdef ENABLE_BINRELOC + +#define br_thread_local_store BR_NAMESPACE(br_thread_local_store) +#define br_locate BR_NAMESPACE(br_locate) +#define br_locate_prefix BR_NAMESPACE(br_locate_prefix) +#define br_prepend_prefix BR_NAMESPACE(br_prepend_prefix) + +#ifndef BR_NO_MACROS + /* These are convenience macros that replace the ones usually used + in Autoconf/Automake projects */ + #undef SELFPATH + #undef PREFIX + #undef PREFIXDIR + #undef BINDIR + #undef SBINDIR + #undef DATADIR + #undef LIBDIR + #undef LIBEXECDIR + #undef ETCDIR + #undef SYSCONFDIR + #undef CONFDIR + #undef LOCALEDIR + + #define SELFPATH (br_thread_local_store (br_locate ((void *) ""))) + #define PREFIX (br_thread_local_store (br_locate_prefix ((void *) ""))) + #define PREFIXDIR (br_thread_local_store (br_locate_prefix ((void *) ""))) + #define BINDIR (br_thread_local_store (br_prepend_prefix ((void *) "", "/bin"))) + #define SBINDIR (br_thread_local_store (br_prepend_prefix ((void *) "", "/sbin"))) + #define DATADIR (br_thread_local_store (br_prepend_prefix ((void *) "", "/share"))) + #define LIBDIR (br_thread_local_store (br_prepend_prefix ((void *) "", "/lib/gaim"))) + #define LIBEXECDIR (br_thread_local_store (br_prepend_prefix ((void *) "", "/libexec"))) + #define ETCDIR (br_thread_local_store (br_prepend_prefix ((void *) "", "/etc"))) + #define SYSCONFDIR (br_thread_local_store (br_prepend_prefix ((void *) "", "/etc"))) + #define CONFDIR (br_thread_local_store (br_prepend_prefix ((void *) "", "/etc"))) + #define LOCALEDIR (br_thread_local_store (br_prepend_prefix ((void *) "", "/share/locale"))) +#endif /* BR_NO_MACROS */ + + +/* The following functions are used internally by BinReloc + and shouldn't be used directly in applications. */ + +char *br_locate (void *symbol); +char *br_locate_prefix (void *symbol); +char *br_prepend_prefix (void *symbol, char *path); + +#endif /* ENABLE_BINRELOC */ + +const char *br_thread_local_store (char *str); + + +/* These macros and functions are not guarded by the ENABLE_BINRELOC + * macro because they are portable. You can use these functions. + */ + +#define br_strcat BR_NAMESPACE(br_strcat) +#define br_extract_dir BR_NAMESPACE(br_extract_dir) +#define br_extract_prefix BR_NAMESPACE(br_extract_prefix) +#define br_set_locate_fallback_func BR_NAMESPACE(br_set_locate_fallback_func) + +#ifndef BR_NO_MACROS + /* Convenience functions for concatenating paths */ + + /* Each time you call one, the previous result will be freed. So don't do this: + * + * some_function( BR_DATADIR("/one"), BR_DATADIR("/two") ) + * + * as the first parameter will now be bogus! + */ + + #define BR_SELFPATH(suffix) (br_thread_local_store (br_strcat (SELFPATH, suffix))) + #define BR_PREFIX(suffix) (br_thread_local_store (br_strcat (PREFIX, suffix))) + #define BR_PREFIXDIR(suffix) (br_thread_local_store (br_strcat (BR_PREFIX, suffix))) + #define BR_BINDIR(suffix) (br_thread_local_store (br_strcat (BINDIR, suffix))) + #define BR_SBINDIR(suffix) (br_thread_local_store (br_strcat (SBINDIR, suffix))) + #define BR_DATADIR(suffix) (br_thread_local_store (br_strcat (DATADIR, suffix))) + #define BR_LIBDIR(suffix) (br_thread_local_store (br_strcat (LIBDIR, suffix))) + #define BR_LIBEXECDIR(suffix) (br_thread_local_store (br_strcat (LIBEXECDIR, suffix))) + #define BR_ETCDIR(suffix) (br_thread_local_store (br_strcat (ETCDIR, suffix))) + #define BR_SYSCONFDIR(suffix) (br_thread_local_store (br_strcat (SYSCONFDIR, suffix))) + #define BR_CONFDIR(suffix) (br_thread_local_store (br_strcat (CONFDIR, suffix))) + #define BR_LOCALEDIR(suffix) (br_thread_local_store (br_strcat (LOCALEDIR, suffix))) +#endif + +char *br_strcat (const char *str1, const char *str2); +char *br_extract_dir (const char *path); +char *br_extract_prefix(const char *path); +typedef char *(*br_locate_fallback_func) (void *symbol, void *data); +void br_set_locate_fallback_func (br_locate_fallback_func func, void *data); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* _PREFIX_H_ */ diff -r d10dda2777a9 -r b63ebf84c42b core/prefs.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/prefs.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,1197 @@ +/* + * 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 + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include "internal.h" +#include "prefs.h" +#include "debug.h" +#include "util.h" + +#ifdef _WIN32 +#include "win32dep.h" +#endif + +struct pref_cb { + GaimPrefCallback func; + gpointer data; + guint id; + void *handle; +}; + +/* TODO: This should use GaimValues? */ +struct gaim_pref { + GaimPrefType type; + char *name; + union { + gpointer generic; + gboolean boolean; + int integer; + char *string; + GList *stringlist; + } value; + GSList *callbacks; + struct gaim_pref *parent; + struct gaim_pref *sibling; + struct gaim_pref *first_child; +}; + + +static struct gaim_pref prefs = { + GAIM_PREF_NONE, + NULL, + { NULL }, + NULL, + NULL, + NULL, + NULL +}; + +static GHashTable *prefs_hash = NULL; +static guint save_timer = 0; +static gboolean prefs_loaded = FALSE; + + +/********************************************************************* + * Private utility functions * + *********************************************************************/ + +static struct +gaim_pref *find_pref(const char *name) +{ + if (!name || name[0] != '/') + return NULL; + else if (name[1] == '\0') + return &prefs; + else + return g_hash_table_lookup(prefs_hash, name); +} + + +/********************************************************************* + * Writing to disk * + *********************************************************************/ + +/* + * This function recursively creates the xmlnode tree from the prefs + * tree structure. Yay recursion! + */ +static void +pref_to_xmlnode(xmlnode *parent, struct gaim_pref *pref) +{ + xmlnode *node, *childnode; + struct gaim_pref *child; + char buf[20]; + GList *cur; + + /* Create a new node */ + node = xmlnode_new_child(parent, "pref"); + xmlnode_set_attrib(node, "name", pref->name); + + /* Set the type of this node (if type == GAIM_PREF_NONE then do nothing) */ + if (pref->type == GAIM_PREF_INT) { + xmlnode_set_attrib(node, "type", "int"); + snprintf(buf, sizeof(buf), "%d", pref->value.integer); + xmlnode_set_attrib(node, "value", buf); + } + else if (pref->type == GAIM_PREF_STRING) { + xmlnode_set_attrib(node, "type", "string"); + xmlnode_set_attrib(node, "value", pref->value.string ? pref->value.string : ""); + } + else if (pref->type == GAIM_PREF_STRING_LIST) { + xmlnode_set_attrib(node, "type", "stringlist"); + for (cur = pref->value.stringlist; cur != NULL; cur = cur->next) + { + childnode = xmlnode_new_child(node, "item"); + xmlnode_set_attrib(childnode, "value", cur->data ? cur->data : ""); + } + } + else if (pref->type == GAIM_PREF_BOOLEAN) { + xmlnode_set_attrib(node, "type", "bool"); + snprintf(buf, sizeof(buf), "%d", pref->value.boolean); + xmlnode_set_attrib(node, "value", buf); + } + + /* All My Children */ + for (child = pref->first_child; child != NULL; child = child->sibling) + pref_to_xmlnode(node, child); +} + +static xmlnode * +prefs_to_xmlnode(void) +{ + xmlnode *node; + struct gaim_pref *pref, *child; + + pref = &prefs; + + /* Create the root preference node */ + node = xmlnode_new("pref"); + xmlnode_set_attrib(node, "version", "1"); + xmlnode_set_attrib(node, "name", "/"); + + /* All My Children */ + for (child = pref->first_child; child != NULL; child = child->sibling) + pref_to_xmlnode(node, child); + + return node; +} + +static void +sync_prefs(void) +{ + xmlnode *node; + char *data; + + if (!prefs_loaded) + { + /* + * TODO: Call schedule_prefs_save()? Ideally we wouldn't need to. + * (prefs.xml should be loaded when gaim_prefs_init is called) + */ + gaim_debug_error("prefs", "Attempted to save prefs before " + "they were read!\n"); + return; + } + + node = prefs_to_xmlnode(); + data = xmlnode_to_formatted_str(node, NULL); + gaim_util_write_data_to_file("prefs.xml", data, -1); + g_free(data); + xmlnode_free(node); +} + +static gboolean +save_cb(gpointer data) +{ + sync_prefs(); + save_timer = 0; + return FALSE; +} + +static void +schedule_prefs_save(void) +{ + if (save_timer == 0) + save_timer = gaim_timeout_add(5000, save_cb, NULL); +} + + +/********************************************************************* + * Reading from disk * + *********************************************************************/ + +static GList *prefs_stack = NULL; + +static void +prefs_start_element_handler (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + gpointer user_data, + GError **error) +{ + GaimPrefType pref_type = GAIM_PREF_NONE; + int i; + const char *pref_name = NULL, *pref_value = NULL; + GString *pref_name_full; + GList *tmp; + + if(strcmp(element_name, "pref") && strcmp(element_name, "item")) + return; + + for(i = 0; attribute_names[i]; i++) { + if(!strcmp(attribute_names[i], "name")) { + pref_name = attribute_values[i]; + } else if(!strcmp(attribute_names[i], "type")) { + if(!strcmp(attribute_values[i], "bool")) + pref_type = GAIM_PREF_BOOLEAN; + else if(!strcmp(attribute_values[i], "int")) + pref_type = GAIM_PREF_INT; + else if(!strcmp(attribute_values[i], "string")) + pref_type = GAIM_PREF_STRING; + else if(!strcmp(attribute_values[i], "stringlist")) + pref_type = GAIM_PREF_STRING_LIST; + else + return; + } else if(!strcmp(attribute_names[i], "value")) { + pref_value = attribute_values[i]; + } + } + + if(!strcmp(element_name, "item")) { + struct gaim_pref *pref; + + pref_name_full = g_string_new(""); + + for(tmp = prefs_stack; tmp; tmp = tmp->next) { + pref_name_full = g_string_prepend(pref_name_full, tmp->data); + pref_name_full = g_string_prepend_c(pref_name_full, '/'); + } + + pref = find_pref(pref_name_full->str); + + if(pref) { + pref->value.stringlist = g_list_append(pref->value.stringlist, + g_strdup(pref_value)); + } + } else { + if(!pref_name || !strcmp(pref_name, "/")) + return; + + pref_name_full = g_string_new(pref_name); + + for(tmp = prefs_stack; tmp; tmp = tmp->next) { + pref_name_full = g_string_prepend_c(pref_name_full, '/'); + pref_name_full = g_string_prepend(pref_name_full, tmp->data); + } + + pref_name_full = g_string_prepend_c(pref_name_full, '/'); + + switch(pref_type) { + case GAIM_PREF_NONE: + gaim_prefs_add_none(pref_name_full->str); + break; + case GAIM_PREF_BOOLEAN: + gaim_prefs_set_bool(pref_name_full->str, atoi(pref_value)); + break; + case GAIM_PREF_INT: + gaim_prefs_set_int(pref_name_full->str, atoi(pref_value)); + break; + case GAIM_PREF_STRING: + gaim_prefs_set_string(pref_name_full->str, pref_value); + break; + case GAIM_PREF_STRING_LIST: + gaim_prefs_set_string_list(pref_name_full->str, NULL); + break; + } + prefs_stack = g_list_prepend(prefs_stack, g_strdup(pref_name)); + g_string_free(pref_name_full, TRUE); + } +} + +static void +prefs_end_element_handler(GMarkupParseContext *context, + const gchar *element_name, + gpointer user_data, GError **error) +{ + if(prefs_stack && !strcmp(element_name, "pref")) { + g_free(prefs_stack->data); + prefs_stack = g_list_delete_link(prefs_stack, prefs_stack); + } +} + +static GMarkupParser prefs_parser = { + prefs_start_element_handler, + prefs_end_element_handler, + NULL, + NULL, + NULL +}; + +gboolean +gaim_prefs_load() +{ + gchar *filename = g_build_filename(gaim_user_dir(), "prefs.xml", NULL); + gchar *contents = NULL; + gsize length; + GMarkupParseContext *context; + GError *error = NULL; + + if (!filename) { + prefs_loaded = TRUE; + return FALSE; + } + + gaim_debug_info("prefs", "Reading %s\n", filename); + + if(!g_file_get_contents(filename, &contents, &length, &error)) { +#ifndef _WIN32 + g_free(filename); + g_error_free(error); + + error = NULL; + + filename = g_build_filename(SYSCONFDIR, "gaim", "prefs.xml", NULL); + + gaim_debug_info("prefs", "Reading %s\n", filename); + + if (!g_file_get_contents(filename, &contents, &length, &error)) { + gaim_debug_error("prefs", "Error reading prefs: %s\n", + error->message); + g_error_free(error); + g_free(filename); + prefs_loaded = TRUE; + + return FALSE; + } +#else /* _WIN32 */ + gaim_debug_error("prefs", "Error reading prefs: %s\n", + error->message); + g_error_free(error); + g_free(filename); + prefs_loaded = TRUE; + + return FALSE; +#endif /* _WIN32 */ + } + + context = g_markup_parse_context_new(&prefs_parser, 0, NULL, NULL); + + if(!g_markup_parse_context_parse(context, contents, length, NULL)) { + g_markup_parse_context_free(context); + g_free(contents); + g_free(filename); + prefs_loaded = TRUE; + + return FALSE; + } + + if(!g_markup_parse_context_end_parse(context, NULL)) { + gaim_debug_error("prefs", "Error parsing %s\n", filename); + g_markup_parse_context_free(context); + g_free(contents); + g_free(filename); + prefs_loaded = TRUE; + + return FALSE; + } + + gaim_debug_info("prefs", "Finished reading %s\n", filename); + g_markup_parse_context_free(context); + g_free(contents); + g_free(filename); + prefs_loaded = TRUE; + + /* I introduced a bug in 2.0.0beta2. This fixes the broken + * scores on upgrade. This can be removed sometime shortly + * after 2.0.0 final is released. -- rlaager */ + if (gaim_prefs_get_int("/core/status/scores/offline") == -500 && + gaim_prefs_get_int("/core/status/scores/available") == 100 && + gaim_prefs_get_int("/core/status/scores/invisible") == -50 && + gaim_prefs_get_int("/core/status/scores/away") == -100 && + gaim_prefs_get_int("/core/status/scores/extended_away") == -200 && + gaim_prefs_get_int("/core/status/scores/idle") == -400) + { + gaim_prefs_set_int("/core/status/scores/idle", -10); + } + + return TRUE; +} + + + +static void +prefs_save_cb(const char *name, GaimPrefType type, gconstpointer val, + gpointer user_data) +{ + + if(!prefs_loaded) + return; + + gaim_debug_misc("prefs", "%s changed, scheduling save.\n", name); + + schedule_prefs_save(); +} + +static char * +get_path_dirname(const char *name) +{ + char *c, *str; + + str = g_strdup(name); + + if ((c = strrchr(str, '/')) != NULL) { + *c = '\0'; + + if (*str == '\0') { + g_free(str); + + str = g_strdup("/"); + } + } + else { + g_free(str); + + str = g_strdup("."); + } + + return str; +} + +static char * +get_path_basename(const char *name) +{ + const char *c; + + if ((c = strrchr(name, '/')) != NULL) + return g_strdup(c + 1); + + return g_strdup(name); +} + +static char * +pref_full_name(struct gaim_pref *pref) +{ + GString *name; + struct gaim_pref *parent; + + if(!pref) + return NULL; + + if(pref == &prefs) + return g_strdup("/"); + + name = g_string_new(pref->name); + parent = pref->parent; + + for(parent = pref->parent; parent && parent->name; parent = parent->parent) { + name = g_string_prepend_c(name, '/'); + name = g_string_prepend(name, parent->name); + } + name = g_string_prepend_c(name, '/'); + return g_string_free(name, FALSE); +} + +static struct gaim_pref * +find_pref_parent(const char *name) +{ + char *parent_name = get_path_dirname(name); + struct gaim_pref *ret = &prefs; + + if(strcmp(parent_name, "/")) { + ret = find_pref(parent_name); + } + + g_free(parent_name); + return ret; +} + +static void +free_pref_value(struct gaim_pref *pref) +{ + switch(pref->type) { + case GAIM_PREF_BOOLEAN: + pref->value.boolean = FALSE; + break; + case GAIM_PREF_INT: + pref->value.integer = 0; + break; + case GAIM_PREF_STRING: + g_free(pref->value.string); + pref->value.string = NULL; + break; + case GAIM_PREF_STRING_LIST: + { + g_list_foreach(pref->value.stringlist, (GFunc)g_free, NULL); + g_list_free(pref->value.stringlist); + } break; + case GAIM_PREF_NONE: + break; + } +} + +static struct gaim_pref * +add_pref(GaimPrefType type, const char *name) +{ + struct gaim_pref *parent; + struct gaim_pref *me; + struct gaim_pref *sibling; + char *my_name; + + parent = find_pref_parent(name); + + if(!parent) + return NULL; + + my_name = get_path_basename(name); + + for(sibling = parent->first_child; sibling; sibling = sibling->sibling) { + if(!strcmp(sibling->name, my_name)) { + g_free(my_name); + return NULL; + } + } + + me = g_new0(struct gaim_pref, 1); + me->type = type; + me->name = my_name; + + me->parent = parent; + if(parent->first_child) { + /* blatant abuse of a for loop */ + for(sibling = parent->first_child; sibling->sibling; + sibling = sibling->sibling); + sibling->sibling = me; + } else { + parent->first_child = me; + } + + g_hash_table_insert(prefs_hash, g_strdup(name), (gpointer)me); + + return me; +} + +void +gaim_prefs_add_none(const char *name) +{ + add_pref(GAIM_PREF_NONE, name); +} + +void +gaim_prefs_add_bool(const char *name, gboolean value) +{ + struct gaim_pref *pref = add_pref(GAIM_PREF_BOOLEAN, name); + + if(!pref) + return; + + pref->value.boolean = value; +} + +void +gaim_prefs_add_int(const char *name, int value) +{ + struct gaim_pref *pref = add_pref(GAIM_PREF_INT, name); + + if(!pref) + return; + + pref->value.integer = value; +} + +void +gaim_prefs_add_string(const char *name, const char *value) +{ + struct gaim_pref *pref = add_pref(GAIM_PREF_STRING, name); + + if(!pref) + return; + + pref->value.string = g_strdup(value); +} + +void +gaim_prefs_add_string_list(const char *name, GList *value) +{ + struct gaim_pref *pref = add_pref(GAIM_PREF_STRING_LIST, name); + GList *tmp; + + if(!pref) + return; + + for(tmp = value; tmp; tmp = tmp->next) + pref->value.stringlist = g_list_append(pref->value.stringlist, + g_strdup(tmp->data)); +} + +static void +remove_pref(struct gaim_pref *pref) +{ + char *name; + + if(!pref || pref == &prefs) + return; + + while(pref->first_child) + remove_pref(pref->first_child); + + if(pref->parent->first_child == pref) { + pref->parent->first_child = pref->sibling; + } else { + struct gaim_pref *sib = pref->parent->first_child; + while(sib && sib->sibling != pref) + sib = sib->sibling; + if(sib) + sib->sibling = pref->sibling; + } + + name = pref_full_name(pref); + + gaim_debug_info("prefs", "removing pref %s\n", name); + + g_hash_table_remove(prefs_hash, name); + g_free(name); + + free_pref_value(pref); + + g_slist_free(pref->callbacks); + g_free(pref->name); + g_free(pref); +} + +void +gaim_prefs_remove(const char *name) +{ + struct gaim_pref *pref = find_pref(name); + + if(!pref) + return; + + remove_pref(pref); +} + +void +gaim_prefs_destroy() +{ + gaim_prefs_remove("/"); +} + +static void +do_callbacks(const char* name, struct gaim_pref *pref) +{ + GSList *cbs; + struct gaim_pref *cb_pref; + for(cb_pref = pref; cb_pref; cb_pref = cb_pref->parent) { + for(cbs = cb_pref->callbacks; cbs; cbs = cbs->next) { + struct pref_cb *cb = cbs->data; + cb->func(name, pref->type, pref->value.generic, cb->data); + } + } +} + +void +gaim_prefs_trigger_callback(const char *name) +{ + struct gaim_pref *pref = find_pref(name); + + if(!pref) { + gaim_debug_error("prefs", + "gaim_prefs_trigger_callback: Unknown pref %s\n", name); + return; + } + + do_callbacks(name, pref); +} + +void +gaim_prefs_set_generic(const char *name, gpointer value) +{ + struct gaim_pref *pref = find_pref(name); + + if(!pref) { + gaim_debug_error("prefs", + "gaim_prefs_set_generic: Unknown pref %s\n", name); + return; + } + + pref->value.generic = value; + do_callbacks(name, pref); +} + +void +gaim_prefs_set_bool(const char *name, gboolean value) +{ + struct gaim_pref *pref = find_pref(name); + + if(pref) { + if(pref->type != GAIM_PREF_BOOLEAN) { + gaim_debug_error("prefs", + "gaim_prefs_set_bool: %s not a boolean pref\n", name); + return; + } + + if(pref->value.boolean != value) { + pref->value.boolean = value; + do_callbacks(name, pref); + } + } else { + gaim_prefs_add_bool(name, value); + } +} + +void +gaim_prefs_set_int(const char *name, int value) +{ + struct gaim_pref *pref = find_pref(name); + + if(pref) { + if(pref->type != GAIM_PREF_INT) { + gaim_debug_error("prefs", + "gaim_prefs_set_int: %s not an integer pref\n", name); + return; + } + + if(pref->value.integer != value) { + pref->value.integer = value; + do_callbacks(name, pref); + } + } else { + gaim_prefs_add_int(name, value); + } +} + +void +gaim_prefs_set_string(const char *name, const char *value) +{ + struct gaim_pref *pref = find_pref(name); + + if(pref) { + if(pref->type != GAIM_PREF_STRING) { + gaim_debug_error("prefs", + "gaim_prefs_set_string: %s not a string pref\n", name); + return; + } + + if((value && !pref->value.string) || + (!value && pref->value.string) || + (value && pref->value.string && + strcmp(pref->value.string, value))) { + g_free(pref->value.string); + pref->value.string = g_strdup(value); + do_callbacks(name, pref); + } + } else { + gaim_prefs_add_string(name, value); + } +} + +void +gaim_prefs_set_string_list(const char *name, GList *value) +{ + struct gaim_pref *pref = find_pref(name); + if(pref) { + GList *tmp; + + if(pref->type != GAIM_PREF_STRING_LIST) { + gaim_debug_error("prefs", + "gaim_prefs_set_string_list: %s not a string list pref\n", + name); + return; + } + + g_list_foreach(pref->value.stringlist, (GFunc)g_free, NULL); + g_list_free(pref->value.stringlist); + pref->value.stringlist = NULL; + + for(tmp = value; tmp; tmp = tmp->next) + pref->value.stringlist = g_list_prepend(pref->value.stringlist, + g_strdup(tmp->data)); + pref->value.stringlist = g_list_reverse(pref->value.stringlist); + + do_callbacks(name, pref); + + } else { + gaim_prefs_add_string_list(name, value); + } +} + +gboolean +gaim_prefs_exists(const char *name) +{ + struct gaim_pref *pref = find_pref(name); + + if (pref != NULL) + return TRUE; + + return FALSE; +} + +GaimPrefType +gaim_prefs_get_type(const char *name) +{ + struct gaim_pref *pref = find_pref(name); + + if (pref == NULL) + return GAIM_PREF_NONE; + + return (pref->type); +} + +gboolean +gaim_prefs_get_bool(const char *name) +{ + struct gaim_pref *pref = find_pref(name); + + if(!pref) { + gaim_debug_error("prefs", + "gaim_prefs_get_bool: Unknown pref %s\n", name); + return FALSE; + } else if(pref->type != GAIM_PREF_BOOLEAN) { + gaim_debug_error("prefs", + "gaim_prefs_get_bool: %s not a boolean pref\n", name); + return FALSE; + } + + return pref->value.boolean; +} + +int +gaim_prefs_get_int(const char *name) +{ + struct gaim_pref *pref = find_pref(name); + + if(!pref) { + gaim_debug_error("prefs", + "gaim_prefs_get_int: Unknown pref %s\n", name); + return 0; + } else if(pref->type != GAIM_PREF_INT) { + gaim_debug_error("prefs", + "gaim_prefs_get_int: %s not an integer pref\n", name); + return 0; + } + + return pref->value.integer; +} + +const char * +gaim_prefs_get_string(const char *name) +{ + struct gaim_pref *pref = find_pref(name); + + if(!pref) { + gaim_debug_error("prefs", + "gaim_prefs_get_string: Unknown pref %s\n", name); + return NULL; + } else if(pref->type != GAIM_PREF_STRING) { + gaim_debug_error("prefs", + "gaim_prefs_get_string: %s not a string pref\n", name); + return NULL; + } + + return pref->value.string; +} + +GList * +gaim_prefs_get_string_list(const char *name) +{ + struct gaim_pref *pref = find_pref(name); + GList *ret = NULL, *tmp; + + if(!pref) { + gaim_debug_error("prefs", + "gaim_prefs_get_string_list: Unknown pref %s\n", name); + return NULL; + } else if(pref->type != GAIM_PREF_STRING_LIST) { + gaim_debug_error("prefs", + "gaim_prefs_get_string_list: %s not a string list pref\n", name); + return NULL; + } + + for(tmp = pref->value.stringlist; tmp; tmp = tmp->next) + ret = g_list_prepend(ret, g_strdup(tmp->data)); + ret = g_list_reverse(ret); + + return ret; +} + +void +gaim_prefs_rename(const char *oldname, const char *newname) +{ + struct gaim_pref *oldpref, *newpref; + + oldpref = find_pref(oldname); + + /* it's already been renamed, call off the dogs */ + if(!oldpref) + return; + + if (oldpref->first_child != NULL) /* can't rename parents */ + { + gaim_debug_error("prefs", "Unable to rename %s to %s: can't rename parents\n", oldname, newname); + return; + } + + + newpref = find_pref(newname); + + if (newpref == NULL) + { + gaim_debug_error("prefs", "Unable to rename %s to %s: new pref not created\n", oldname, newname); + return; + } + + if (oldpref->type != newpref->type) + { + gaim_debug_error("prefs", "Unable to rename %s to %s: differing types\n", oldname, newname); + return; + } + + gaim_debug_info("prefs", "Renaming %s to %s\n", oldname, newname); + + switch(oldpref->type) { + case GAIM_PREF_NONE: + break; + case GAIM_PREF_BOOLEAN: + gaim_prefs_set_bool(newname, oldpref->value.boolean); + break; + case GAIM_PREF_INT: + gaim_prefs_set_int(newname, oldpref->value.integer); + break; + case GAIM_PREF_STRING: + gaim_prefs_set_string(newname, oldpref->value.string); + break; + case GAIM_PREF_STRING_LIST: + gaim_prefs_set_string_list(newname, oldpref->value.stringlist); + break; + } + + remove_pref(oldpref); +} + +void +gaim_prefs_rename_boolean_toggle(const char *oldname, const char *newname) +{ + struct gaim_pref *oldpref, *newpref; + + oldpref = find_pref(oldname); + + /* it's already been renamed, call off the cats */ + if(!oldpref) + return; + + if (oldpref->type != GAIM_PREF_BOOLEAN) + { + gaim_debug_error("prefs", "Unable to rename %s to %s: old pref not a boolean\n", oldname, newname); + return; + } + + if (oldpref->first_child != NULL) /* can't rename parents */ + { + gaim_debug_error("prefs", "Unable to rename %s to %s: can't rename parents\n", oldname, newname); + return; + } + + + newpref = find_pref(newname); + + if (newpref == NULL) + { + gaim_debug_error("prefs", "Unable to rename %s to %s: new pref not created\n", oldname, newname); + return; + } + + if (oldpref->type != newpref->type) + { + gaim_debug_error("prefs", "Unable to rename %s to %s: differing types\n", oldname, newname); + return; + } + + gaim_debug_info("prefs", "Renaming and toggling %s to %s\n", oldname, newname); + gaim_prefs_set_bool(newname, !(oldpref->value.boolean)); + + remove_pref(oldpref); +} + +guint +gaim_prefs_connect_callback(void *handle, const char *name, GaimPrefCallback func, gpointer data) +{ + struct gaim_pref *pref; + struct pref_cb *cb; + static guint cb_id = 0; + + pref = find_pref(name); + if (pref == NULL) + return 0; + + cb = g_new0(struct pref_cb, 1); + + cb->func = func; + cb->data = data; + cb->id = ++cb_id; + cb->handle = handle; + + pref->callbacks = g_slist_append(pref->callbacks, cb); + + return cb->id; +} + +static gboolean +disco_callback_helper(struct gaim_pref *pref, guint callback_id) +{ + GSList *cbs; + struct gaim_pref *child; + + if(!pref) + return FALSE; + + for(cbs = pref->callbacks; cbs; cbs = cbs->next) { + struct pref_cb *cb = cbs->data; + if(cb->id == callback_id) { + pref->callbacks = g_slist_delete_link(pref->callbacks, cbs); + g_free(cb); + return TRUE; + } + } + + for(child = pref->first_child; child; child = child->sibling) { + if(disco_callback_helper(child, callback_id)) + return TRUE; + } + + return FALSE; +} + +void +gaim_prefs_disconnect_callback(guint callback_id) +{ + disco_callback_helper(&prefs, callback_id); +} + +static void +disco_callback_helper_handle(struct gaim_pref *pref, void *handle) +{ + GSList *cbs; + struct gaim_pref *child; + + if(!pref) + return; + + cbs = pref->callbacks; + while (cbs != NULL) { + struct pref_cb *cb = cbs->data; + if(cb->handle == handle) { + pref->callbacks = g_slist_delete_link(pref->callbacks, cbs); + g_free(cb); + cbs = pref->callbacks; + } else + cbs = cbs->next; + } + + for(child = pref->first_child; child; child = child->sibling) + disco_callback_helper_handle(child, handle); +} + +void +gaim_prefs_disconnect_by_handle(void *handle) +{ + g_return_if_fail(handle != NULL); + + disco_callback_helper_handle(&prefs, handle); +} + +void +gaim_prefs_update_old() +{ + /* Remove some no-longer-used prefs */ + gaim_prefs_remove("/core/away/auto_response/enabled"); + gaim_prefs_remove("/core/away/auto_response/idle_only"); + gaim_prefs_remove("/core/away/auto_response/in_active_conv"); + gaim_prefs_remove("/core/away/auto_response/sec_before_resend"); + gaim_prefs_remove("/core/away/auto_response"); + gaim_prefs_remove("/core/away/default_message"); + gaim_prefs_remove("/core/buddies/use_server_alias"); + gaim_prefs_remove("/core/conversations/away_back_on_send"); + gaim_prefs_remove("/core/conversations/send_urls_as_links"); + gaim_prefs_remove("/core/conversations/im/show_login"); + gaim_prefs_remove("/core/conversations/chat/show_join"); + gaim_prefs_remove("/core/conversations/chat/show_leave"); + gaim_prefs_remove("/core/conversations/combine_chat_im"); + gaim_prefs_remove("/core/conversations/use_alias_for_title"); + gaim_prefs_remove("/core/logging/log_signon_signoff"); + gaim_prefs_remove("/core/logging/log_idle_state"); + gaim_prefs_remove("/core/logging/log_away_state"); + gaim_prefs_remove("/core/logging/log_own_states"); + gaim_prefs_remove("/core/status/scores/hidden"); + gaim_prefs_remove("/plugins/core/autorecon/hide_connected_error"); + gaim_prefs_remove("/plugins/core/autorecon/hide_connecting_error"); + gaim_prefs_remove("/plugins/core/autorecon/hide_reconnecting_dialog"); + gaim_prefs_remove("/plugins/core/autorecon/restore_state"); + gaim_prefs_remove("/plugins/core/autorecon"); +} + +void * +gaim_prefs_get_handle(void) +{ + static int handle; + + return &handle; +} + +void +gaim_prefs_init(void) +{ + void *handle = gaim_prefs_get_handle(); + + prefs_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); + + gaim_prefs_connect_callback(handle, "/", prefs_save_cb, NULL); + + gaim_prefs_add_none("/core"); + gaim_prefs_add_none("/plugins"); + gaim_prefs_add_none("/plugins/core"); + gaim_prefs_add_none("/plugins/lopl"); + gaim_prefs_add_none("/plugins/prpl"); + + /* Away */ + gaim_prefs_add_none("/core/away"); + gaim_prefs_add_string("/core/away/idle_reporting", "system"); + gaim_prefs_add_bool("/core/away/away_when_idle", TRUE); + gaim_prefs_add_int("/core/away/mins_before_away", 5); + + /* Away -> Auto-Reply */ + if (!gaim_prefs_exists("/core/away/auto_response/enabled") || + !gaim_prefs_exists("/core/away/auto_response/idle_only")) + { + gaim_prefs_add_string("/core/away/auto_reply", "awayidle"); + } + else + { + if (!gaim_prefs_get_bool("/core/away/auto_response/enabled")) + { + gaim_prefs_add_string("/core/away/auto_reply", "never"); + } + else + { + if (gaim_prefs_get_bool("/core/away/auto_response/idle_only")) + { + gaim_prefs_add_string("/core/away/auto_reply", "awayidle"); + } + else + { + gaim_prefs_add_string("/core/away/auto_reply", "away"); + } + } + } + + /* Buddies */ + gaim_prefs_add_none("/core/buddies"); + + /* Contact Priority Settings */ + gaim_prefs_add_none("/core/contact"); + gaim_prefs_add_bool("/core/contact/last_match", FALSE); + gaim_prefs_remove("/core/contact/offline_score"); + gaim_prefs_remove("/core/contact/away_score"); + gaim_prefs_remove("/core/contact/idle_score"); +} + +void +gaim_prefs_uninit() +{ + if (save_timer != 0) + { + gaim_timeout_remove(save_timer); + save_timer = 0; + sync_prefs(); + } + + gaim_prefs_disconnect_by_handle(gaim_prefs_get_handle()); +} diff -r d10dda2777a9 -r b63ebf84c42b core/prefs.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/prefs.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,279 @@ +/** + * @file prefs.h Prefs API + * @ingroup core + * + * 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 + * + */ +#ifndef _GAIM_PREFS_H_ +#define _GAIM_PREFS_H_ + +#include + +/** + * String format for preferences. + */ +typedef enum +{ + GAIM_STRING_FORMAT_TYPE_NONE = 0, + GAIM_STRING_FORMAT_TYPE_MULTILINE = 1 << 0, + GAIM_STRING_FORMAT_TYPE_HTML = 1 << 1 +} GaimStringFormatType; + +/** + * Pref data types. + */ +typedef enum _GaimPrefType +{ + GAIM_PREF_NONE, + GAIM_PREF_BOOLEAN, + GAIM_PREF_INT, + GAIM_PREF_STRING, + GAIM_PREF_STRING_LIST + +} GaimPrefType; + +/** + * Pref change callback type + */ + +typedef void (*GaimPrefCallback) (const char *name, GaimPrefType type, + gconstpointer val, gpointer data); + +#ifdef __cplusplus +extern "C" { +#endif + +/**************************************************************************/ +/** @name Prefs API */ +/**************************************************************************/ +/*@{*/ + +/** + * Returns the prefs subsystem handle. + * + * @return The prefs subsystem handle. + */ +void *gaim_prefs_get_handle(void); + +/** + * Initialize core prefs + */ +void gaim_prefs_init(void); + +/** + * Uninitializes the prefs subsystem. + */ +void gaim_prefs_uninit(void); + +/** + * Add a new typeless pref. + * + * @param name The name of the pref + */ +void gaim_prefs_add_none(const char *name); + +/** + * Add a new boolean pref. + * + * @param name The name of the pref + * @param value The initial value to set + */ +void gaim_prefs_add_bool(const char *name, gboolean value); + +/** + * Add a new integer pref. + * + * @param name The name of the pref + * @param value The initial value to set + */ +void gaim_prefs_add_int(const char *name, int value); + +/** + * Add a new string pref. + * + * @param name The name of the pref + * @param value The initial value to set + */ +void gaim_prefs_add_string(const char *name, const char *value); + +/** + * Add a new string list pref. + * + * @param name The name of the pref + * @param value The initial value to set + */ +void gaim_prefs_add_string_list(const char *name, GList *value); + +/** + * Remove a pref. + * + * @param name The name of the pref + */ +void gaim_prefs_remove(const char *name); + +/** + * Rename a pref + * + * @param oldname The old name of the pref + * @param newname The new name for the pref + */ +void gaim_prefs_rename(const char *oldname, const char *newname); + +/** + * Rename a boolean pref, toggling it's value + * + * @param oldname The old name of the pref + * @param newname The new name for the pref + */ +void gaim_prefs_rename_boolean_toggle(const char *oldname, const char *newname); + +/** + * Remove all prefs. + */ +void gaim_prefs_destroy(void); + +/** + * Set raw pref value + * + * @param name The name of the pref + * @param value The value to set + */ +void gaim_prefs_set_generic(const char *name, gpointer value); + +/** + * Set boolean pref value + * + * @param name The name of the pref + * @param value The value to set + */ +void gaim_prefs_set_bool(const char *name, gboolean value); + +/** + * Set integer pref value + * + * @param name The name of the pref + * @param value The value to set + */ +void gaim_prefs_set_int(const char *name, int value); + +/** + * Set string pref value + * + * @param name The name of the pref + * @param value The value to set + */ +void gaim_prefs_set_string(const char *name, const char *value); + +/** + * Set string pref value + * + * @param name The name of the pref + * @param value The value to set + */ +void gaim_prefs_set_string_list(const char *name, GList *value); + +/** + * Check if a pref exists + * + * @param name The name of the pref + * @return TRUE if the pref exists. Otherwise FALSE. + */ +gboolean gaim_prefs_exists(const char *name); + +/** + * Get pref type + * + * @param name The name of the pref + * @return The type of the pref + */ +GaimPrefType gaim_prefs_get_type(const char *name); + +/** + * Get boolean pref value + * + * @param name The name of the pref + * @return The value of the pref + */ +gboolean gaim_prefs_get_bool(const char *name); + +/** + * Get integer pref value + * + * @param name The name of the pref + * @return The value of the pref + */ +int gaim_prefs_get_int(const char *name); + +/** + * Get string pref value + * + * @param name The name of the pref + * @return The value of the pref + */ +const char *gaim_prefs_get_string(const char *name); + +/** + * Get string list pref value + * + * @param name The name of the pref + * @return The value of the pref + */ +GList *gaim_prefs_get_string_list(const char *name); + +/** + * Add a callback to a pref (and its children) + */ +guint gaim_prefs_connect_callback(void *handle, const char *name, GaimPrefCallback cb, + gpointer data); + +/** + * Remove a callback to a pref + */ +void gaim_prefs_disconnect_callback(guint callback_id); + +/** + * Remove all pref callbacks by handle + */ +void gaim_prefs_disconnect_by_handle(void *handle); + +/** + * Trigger callbacks as if the pref changed + */ +void gaim_prefs_trigger_callback(const char *name); + +/** + * Read preferences + */ +gboolean gaim_prefs_load(void); + +/** + * Rename legacy prefs and delete some that no longer exist. + */ +void gaim_prefs_update_old(void); + +/*@}*/ + +#ifdef __cplusplus +} +#endif + +#endif /* _GAIM_PREFS_H_ */ diff -r d10dda2777a9 -r b63ebf84c42b core/privacy.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/privacy.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,250 @@ +/** + * 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 "account.h" +#include "privacy.h" +#include "server.h" +#include "util.h" + +static GaimPrivacyUiOps *privacy_ops = NULL; + +gboolean +gaim_privacy_permit_add(GaimAccount *account, const char *who, + gboolean local_only) +{ + GSList *l; + char *name; + GaimBuddy *buddy; + + g_return_val_if_fail(account != NULL, FALSE); + g_return_val_if_fail(who != NULL, FALSE); + + name = g_strdup(gaim_normalize(account, who)); + + for (l = account->permit; l != NULL; l = l->next) { + if (!gaim_utf8_strcasecmp(name, (char *)l->data)) + break; + } + + if (l != NULL) + { + g_free(name); + return FALSE; + } + + account->permit = g_slist_append(account->permit, name); + + if (!local_only && gaim_account_is_connected(account)) + serv_add_permit(gaim_account_get_connection(account), who); + + if (privacy_ops != NULL && privacy_ops->permit_added != NULL) + privacy_ops->permit_added(account, who); + + gaim_blist_schedule_save(); + + /* This lets the UI know a buddy has had its privacy setting changed */ + buddy = gaim_find_buddy(account, name); + if (buddy != NULL) { + gaim_signal_emit(gaim_blist_get_handle(), + "buddy-privacy-changed", buddy); + } + return TRUE; +} + +gboolean +gaim_privacy_permit_remove(GaimAccount *account, const char *who, + gboolean local_only) +{ + GSList *l; + char *name; + GaimBuddy *buddy; + + g_return_val_if_fail(account != NULL, FALSE); + g_return_val_if_fail(who != NULL, FALSE); + + name = gaim_normalize(account, who); + + for (l = account->permit; l != NULL; l = l->next) { + if (!gaim_utf8_strcasecmp(name, (char *)l->data)) + break; + } + + if (l == NULL) + return FALSE; + + g_free(l->data); + account->permit = g_slist_delete_link(account->permit, l); + + if (!local_only && gaim_account_is_connected(account)) + serv_rem_permit(gaim_account_get_connection(account), who); + + if (privacy_ops != NULL && privacy_ops->permit_removed != NULL) + privacy_ops->permit_removed(account, who); + + gaim_blist_schedule_save(); + + buddy = gaim_find_buddy(account, name); + if (buddy != NULL) { + gaim_signal_emit(gaim_blist_get_handle(), + "buddy-privacy-changed", buddy); + } + return TRUE; +} + +gboolean +gaim_privacy_deny_add(GaimAccount *account, const char *who, + gboolean local_only) +{ + GSList *l; + char *name; + GaimBuddy *buddy; + + g_return_val_if_fail(account != NULL, FALSE); + g_return_val_if_fail(who != NULL, FALSE); + + name = g_strdup(gaim_normalize(account, who)); + + for (l = account->deny; l != NULL; l = l->next) { + if (!gaim_utf8_strcasecmp(name, gaim_normalize(account, (char *)l->data))) + break; + } + + if (l != NULL) + { + g_free(name); + return FALSE; + } + + account->deny = g_slist_append(account->deny, name); + + if (!local_only && gaim_account_is_connected(account)) + serv_add_deny(gaim_account_get_connection(account), who); + + if (privacy_ops != NULL && privacy_ops->deny_added != NULL) + privacy_ops->deny_added(account, who); + + gaim_blist_schedule_save(); + + buddy = gaim_find_buddy(account, name); + if (buddy != NULL) { + gaim_signal_emit(gaim_blist_get_handle(), + "buddy-privacy-changed", buddy); + } + return TRUE; +} + +gboolean +gaim_privacy_deny_remove(GaimAccount *account, const char *who, + gboolean local_only) +{ + GSList *l; + char *name; + GaimBuddy *buddy; + + g_return_val_if_fail(account != NULL, FALSE); + g_return_val_if_fail(who != NULL, FALSE); + + name = gaim_normalize(account, who); + + for (l = account->deny; l != NULL; l = l->next) { + if (!gaim_utf8_strcasecmp(name, (char *)l->data)) + break; + } + + buddy = gaim_find_buddy(account, name); + + if (l == NULL) + return FALSE; + + name = l->data; + account->deny = g_slist_delete_link(account->deny, l); + + if (!local_only && gaim_account_is_connected(account)) + serv_rem_deny(gaim_account_get_connection(account), name); + + if (privacy_ops != NULL && privacy_ops->deny_removed != NULL) + privacy_ops->deny_removed(account, who); + + if (buddy != NULL) { + gaim_signal_emit(gaim_blist_get_handle(), + "buddy-privacy-changed", buddy); + } + + g_free(name); + gaim_blist_schedule_save(); + + return TRUE; +} + +gboolean +gaim_privacy_check(GaimAccount *account, const char *who) +{ + GSList *list; + + switch (account->perm_deny) { + case GAIM_PRIVACY_ALLOW_ALL: + return TRUE; + + case GAIM_PRIVACY_DENY_ALL: + return FALSE; + + case GAIM_PRIVACY_ALLOW_USERS: + who = gaim_normalize(account, who); + for (list=account->permit; list!=NULL; list=list->next) { + if (!gaim_utf8_strcasecmp(who, (char *)list->data)) + return TRUE; + } + return FALSE; + + case GAIM_PRIVACY_DENY_USERS: + who = gaim_normalize(account, who); + for (list=account->deny; list!=NULL; list=list->next) { + if (!gaim_utf8_strcasecmp(who, (char *)list->data )) + return FALSE; + } + return TRUE; + + case GAIM_PRIVACY_ALLOW_BUDDYLIST: + return (gaim_find_buddy(account, who) != NULL); + + default: + g_return_val_if_reached(TRUE); + } +} + +void +gaim_privacy_set_ui_ops(GaimPrivacyUiOps *ops) +{ + privacy_ops = ops; +} + +GaimPrivacyUiOps * +gaim_privacy_get_ui_ops(void) +{ + return privacy_ops; +} + +void +gaim_privacy_init(void) +{ +} diff -r d10dda2777a9 -r b63ebf84c42b core/privacy.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/privacy.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,144 @@ +/** + * @file privacy.h Privacy API + * @ingroup core + * + * 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 + */ +#ifndef _GAIM_PRIVACY_H_ +#define _GAIM_PRIVACY_H_ + +#include "account.h" + +/** + * Privacy data types. + */ +typedef enum _GaimPrivacyType +{ + GAIM_PRIVACY_ALLOW_ALL = 1, + GAIM_PRIVACY_DENY_ALL, + GAIM_PRIVACY_ALLOW_USERS, + GAIM_PRIVACY_DENY_USERS, + GAIM_PRIVACY_ALLOW_BUDDYLIST +} GaimPrivacyType; + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Privacy core/UI operations. + */ +typedef struct +{ + void (*permit_added)(GaimAccount *account, const char *name); + void (*permit_removed)(GaimAccount *account, const char *name); + void (*deny_added)(GaimAccount *account, const char *name); + void (*deny_removed)(GaimAccount *account, const char *name); + +} GaimPrivacyUiOps; + +/** + * Adds a user to the account's permit list. + * + * @param account The account. + * @param name The name of the user to add to the list. + * @param local_only If TRUE, only the local list is updated, and not + * the server. + * + * @return TRUE if the user was added successfully, or @c FALSE otherwise. + */ +gboolean gaim_privacy_permit_add(GaimAccount *account, const char *name, + gboolean local_only); + +/** + * Removes a user from the account's permit list. + * + * @param account The account. + * @param name The name of the user to add to the list. + * @param local_only If TRUE, only the local list is updated, and not + * the server. + * + * @return TRUE if the user was removed successfully, or @c FALSE otherwise. + */ +gboolean gaim_privacy_permit_remove(GaimAccount *account, const char *name, + gboolean local_only); + +/** + * Adds a user to the account's deny list. + * + * @param account The account. + * @param name The name of the user to add to the list. + * @param local_only If TRUE, only the local list is updated, and not + * the server. + * + * @return TRUE if the user was added successfully, or @c FALSE otherwise. + */ +gboolean gaim_privacy_deny_add(GaimAccount *account, const char *name, + gboolean local_only); + +/** + * Removes a user from the account's deny list. + * + * @param account The account. + * @param name The name of the user to add to the list. + * @param local_only If TRUE, only the local list is updated, and not + * the server. + * + * @return TRUE if the user was removed successfully, or @c FALSE otherwise. + */ +gboolean gaim_privacy_deny_remove(GaimAccount *account, const char *name, + gboolean local_only); + + +/** + * Check the privacy-setting for a user. + * + * @param account The account. + * @param who The name of the user. + * + * @return @c FALSE if the specified account's privacy settings block the user or @c TRUE otherwise. The meaning of "block" is protocol-dependent and generally relates to status and/or sending of messages. + */ +gboolean gaim_privacy_check(GaimAccount *account, const char *who); + +/** + * Sets the UI operations structure for the privacy subsystem. + * + * @param ops The UI operations structure. + */ +void gaim_privacy_set_ui_ops(GaimPrivacyUiOps *ops); + +/** + * Returns the UI operations structure for the privacy subsystem. + * + * @return The UI operations structure. + */ +GaimPrivacyUiOps *gaim_privacy_get_ui_ops(void); + +/** + * Initializes the privacy subsystem. + */ +void gaim_privacy_init(void); + +#ifdef __cplusplus +} +#endif + +#endif /* _GAIM_PRIVACY_H_ */ diff -r d10dda2777a9 -r b63ebf84c42b core/protocols/Makefile.am --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/protocols/Makefile.am Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,3 @@ +DIST_SUBDIRS = bonjour gg irc jabber msn novell oscar qq sametime silc toc simple yahoo zephyr + +SUBDIRS = $(DYNAMIC_PRPLS) $(STATIC_PRPLS) diff -r d10dda2777a9 -r b63ebf84c42b core/protocols/bonjour/Makefile.am --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/protocols/bonjour/Makefile.am Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,41 @@ +EXTRA_DIST = \ + Makefile.mingw + +pkgdir = $(libdir)/gaim + +BONJOURSOURCES = \ + bonjour.c \ + bonjour.h \ + buddy.c \ + buddy.h \ + dns_sd.c \ + dns_sd.h \ + jabber.c \ + jabber.h + +AM_CFLAGS = $(st) + +libbonjour_la_LDFLAGS = -module -avoid-version $(GLIB_LIBS) $(HOWL_LIBS) + +if STATIC_BONJOUR + +st = -DGAIM_STATIC_PRPL +noinst_LIBRARIES = libbonjour.a +libbonjour_a_SOURCES = $(BONJOURSOURCES) +libbonjour_a_CFLAGS = $(AM_CFLAGS) +libbonjour_a_LIBADD = $(HOWL_LIBS) + +else + +st = +pkg_LTLIBRARIES = libbonjour.la +libbonjour_la_SOURCES = $(BONJOURSOURCES) + +endif + + +AM_CPPFLAGS = \ + -I$(top_srcdir)/core \ + $(GLIB_CFLAGS) \ + $(DEBUG_CFLAGS) \ + $(HOWL_CFLAGS) diff -r d10dda2777a9 -r b63ebf84c42b core/protocols/bonjour/Makefile.mingw --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/protocols/bonjour/Makefile.mingw Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,143 @@ +# +# Makefile.mingw +# +# Description: Makefile for win32 (mingw) version of libbonjour +# + +# +# PATHS +# + +INCLUDE_DIR := . +GTK_TOP := ../../../../win32-dev/gtk_2_0 +GAIM_TOP := ../../.. +BONJOUR_ROOT := . +GAIM_INSTALL_DIR := $(GAIM_TOP)/win32-install-dir +HOWL_DIR := $(GAIM_TOP)/../win32-dev/howl-1.0.0 + +## +## VARIABLE DEFINITIONS +## + +TARGET = libbonjour + +NEEDED_DLLS = $(HOWL_DIR)/bin/libhowl-1.dll + +# Compiler Options + +CFLAGS = + +DEFINES = + +# Static or Plugin... +ifeq ($(TYPE),STATIC) + DEFINES += -DSTATIC + DLL_INSTALL_DIR = $(GAIM_INSTALL_DIR) +else +ifeq ($(TYPE),PLUGIN) + DLL_INSTALL_DIR = $(GAIM_INSTALL_DIR)/plugins +endif +endif + + +## +## INCLUDE MAKEFILES +## + +include $(GAIM_TOP)/src/win32/global.mak + +## +## INCLUDE PATHS +## + +INCLUDE_PATHS += -I$(BONJOUR_ROOT) \ + -I$(GTK_TOP)/include \ + -I$(GTK_TOP)/include/gtk-2.0 \ + -I$(GTK_TOP)/include/glib-2.0 \ + -I$(GTK_TOP)/include/pango-1.0 \ + -I$(GTK_TOP)/include/atk-1.0 \ + -I$(GTK_TOP)/lib/glib-2.0/include \ + -I$(GTK_TOP)/lib/gtk-2.0/include \ + -I$(HOWL_DIR)/include/howl \ + -I$(GAIM_TOP)/src \ + -I$(GAIM_TOP)/src/win32 \ + -I$(GAIM_TOP) + + +LIB_PATHS = -L$(GTK_TOP)/lib \ + -L$(HOWL_DIR)/lib \ + -L$(GAIM_TOP)/src + + +## +## SOURCES, OBJECTS +## + +C_SRC = bonjour.c \ + buddy.c \ + dns_sd.c \ + jabber.c + + +OBJECTS = $(C_SRC:%.c=%.o) + + +## +## LIBRARIES +## + +LIBS = -lgtk-win32-2.0 \ + -lglib-2.0 \ + -lgdk-win32-2.0 \ + -lgmodule-2.0 \ + -lgobject-2.0 \ + -lws2_32 \ + -lintl \ + -lhowl \ + -lgaim + + +## +## RULES +## + +# How to make a C file + +%.o: %.c + $(CC) $(CFLAGS) $(DEFINES) $(INCLUDE_PATHS) -o $@ -c $< + +## +## TARGET DEFINITIONS +## + +.PHONY: all clean + +all: $(TARGET).dll + +install: + cp $(BONJOUR_ROOT)/$(TARGET).dll $(DLL_INSTALL_DIR) + cp $(NEEDED_DLLS) $(GAIM_INSTALL_DIR) + + +## +## BUILD Dependencies +## + +$(GAIM_TOP)/src/gaim.lib: + $(MAKE) -C $(GAIM_TOP)/src -f Makefile.mingw gaim.lib + +## +## BUILD DLL +## + +$(TARGET).dll: $(OBJECTS) $(GAIM_TOP)/src/gaim.lib + $(CC) -shared $(OBJECTS) $(LIB_PATHS) $(LIBS) $(DLL_LD_FLAGS) -Wl,--out-implib,$(TARGET).lib -o $(TARGET).dll + +## +## CLEAN RULES +## + +clean: + rm -rf *.o + rm -rf $(TARGET).dll + rm -rf $(TARGET).lib diff -r d10dda2777a9 -r b63ebf84c42b core/protocols/bonjour/bonjour.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/protocols/bonjour/bonjour.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,598 @@ +/* + * gaim - Bonjour Protocol Plugin + * + * 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 +#ifndef _WIN32 +#include +#else +#define UNICODE +#include +#include +#endif + +#include "internal.h" + +#include "account.h" +#include "accountopt.h" +#include "debug.h" +#include "util.h" +#include "version.h" + +#include "bonjour.h" +#include "dns_sd.h" +#include "jabber.h" +#include "buddy.h" + +/* + * TODO: Should implement an add_buddy callback that removes the buddy + * from the local list. Bonjour manages buddies for you, and + * adding someone locally by hand is stupid. Or, maybe even better, + * if a PRPL does not have an add_buddy callback then do not allow + * users to add buddies. + */ + +static char *default_firstname; +static char *default_lastname; +static char *default_hostname; + +static void +bonjour_removeallfromlocal(GaimConnection *gc) +{ + GaimAccount *account = gaim_connection_get_account(gc); + GaimBuddyList *blist; + GaimBlistNode *gnode, *cnode, *bnode; + GaimBuddy *buddy; + + blist = gaim_get_blist(); + if (blist == NULL) + return; + + /* Go through and remove all buddies that belong to this account */ + for (gnode = blist->root; gnode; gnode = gnode->next) + { + if (!GAIM_BLIST_NODE_IS_GROUP(gnode)) + continue; + for (cnode = gnode->child; cnode; cnode = cnode->next) + { + if (!GAIM_BLIST_NODE_IS_CONTACT(cnode)) + continue; + for (bnode = cnode->child; bnode; bnode = bnode->next) + { + if (!GAIM_BLIST_NODE_IS_BUDDY(bnode)) + continue; + buddy = (GaimBuddy *)bnode; + if (buddy->account != account) + continue; + gaim_prpl_got_user_status(account, buddy->name, "offline", NULL); + gaim_blist_remove_buddy(buddy); + } + } + } +} + +static void +bonjour_login(GaimAccount *account) +{ + GaimConnection *gc = gaim_account_get_connection(account); + GaimGroup *bonjour_group = NULL; + BonjourData *bd = NULL; + GaimStatus *status; + GaimPresence *presence; + + gc->flags |= GAIM_CONNECTION_HTML; + gc->proto_data = g_new0(BonjourData, 1); + bd = gc->proto_data; + + /* Start waiting for jabber connections (iChat style) */ + bd->jabber_data = g_new(BonjourJabber, 1); + bd->jabber_data->port = BONJOUR_DEFAULT_PORT_INT; + bd->jabber_data->account = account; + + if (bonjour_jabber_start(bd->jabber_data) == -1) { + /* Send a message about the connection error */ + gaim_connection_error(gc, _("Unable to listen for incoming IM connections\n")); + + /* Free the data */ + g_free(bd->jabber_data); + bd->jabber_data = NULL; + return; + } + + /* Connect to the mDNS daemon looking for buddies in the LAN */ + bd->dns_sd_data = bonjour_dns_sd_new(); + bd->dns_sd_data->name = (sw_string)gaim_account_get_username(account); + bd->dns_sd_data->txtvers = g_strdup("1"); + bd->dns_sd_data->version = g_strdup("1"); + bd->dns_sd_data->first = g_strdup(gaim_account_get_string(account, "first", default_firstname)); + bd->dns_sd_data->last = g_strdup(gaim_account_get_string(account, "last", default_lastname)); + bd->dns_sd_data->port_p2pj = bd->jabber_data->port; + bd->dns_sd_data->phsh = g_strdup(""); + bd->dns_sd_data->email = g_strdup(gaim_account_get_string(account, "email", "")); + bd->dns_sd_data->vc = g_strdup(""); + bd->dns_sd_data->jid = g_strdup(gaim_account_get_string(account, "jid", "")); + bd->dns_sd_data->AIM = g_strdup(gaim_account_get_string(account, "AIM", "")); + + status = gaim_account_get_active_status(account); + presence = gaim_account_get_presence(account); + if (gaim_presence_is_available(presence)) + bd->dns_sd_data->status = g_strdup("avail"); + else if (gaim_presence_is_idle(presence)) + bd->dns_sd_data->status = g_strdup("away"); + else + bd->dns_sd_data->status = g_strdup("dnd"); + bd->dns_sd_data->msg = g_strdup(gaim_status_get_attr_string(status, "message")); + + bd->dns_sd_data->account = account; + if (!bonjour_dns_sd_start(bd->dns_sd_data)) + { + gaim_connection_error(gc, _("Unable to establish connection with the local mDNS server. Is it running?")); + return; + } + + /* Create a group for bonjour buddies */ + bonjour_group = gaim_group_new(BONJOUR_GROUP_NAME); + gaim_blist_add_group(bonjour_group, NULL); + + /* Show the buddy list by telling Gaim we have already connected */ + gaim_connection_set_state(gc, GAIM_CONNECTED); +} + +static void +bonjour_close(GaimConnection *connection) +{ + GaimGroup *bonjour_group; + BonjourData *bd = (BonjourData*)connection->proto_data; + + /* Stop looking for buddies in the LAN */ + if (bd->dns_sd_data != NULL) + { + bonjour_dns_sd_stop(bd->dns_sd_data); + bonjour_dns_sd_free(bd->dns_sd_data); + } + + if (bd->jabber_data != NULL) + { + /* Stop waiting for conversations */ + bonjour_jabber_stop(bd->jabber_data); + g_free(bd->jabber_data); + } + + /* Remove all the bonjour buddies */ + bonjour_removeallfromlocal(connection); + + /* Delete the bonjour group */ + bonjour_group = gaim_find_group(BONJOUR_GROUP_NAME); + if (bonjour_group != NULL) + gaim_blist_remove_group(bonjour_group); + +} + +static const char * +bonjour_list_icon(GaimAccount *account, GaimBuddy *buddy) +{ + return BONJOUR_ICON_NAME; +} + +static int +bonjour_send_im(GaimConnection *connection, const char *to, const char *msg, GaimMessageFlags flags) +{ + if(!to || !msg) + return 0; + + return bonjour_jabber_send_message(((BonjourData*)(connection->proto_data))->jabber_data, to, msg); +} + +static void +bonjour_set_status(GaimAccount *account, GaimStatus *status) +{ + GaimConnection *gc; + BonjourData *bd; + gboolean disconnected; + GaimStatusType *type; + int primitive; + GaimPresence *presence; + const char *message, *bonjour_status; + gchar *stripped; + + gc = gaim_account_get_connection(account); + bd = gc->proto_data; + disconnected = gaim_account_is_disconnected(account); + type = gaim_status_get_type(status); + primitive = gaim_status_type_get_primitive(type); + presence = gaim_account_get_presence(account); + + message = gaim_status_get_attr_string(status, "message"); + if (message == NULL) + message = ""; + stripped = gaim_markup_strip_html(message); + + /* + * The three possible status for Bonjour are + * -available ("avail") + * -idle ("away") + * -away ("dnd") + * Each of them can have an optional message. + */ + if (gaim_presence_is_available(presence)) + bonjour_status = "avail"; + else if (gaim_presence_is_idle(presence)) + bonjour_status = "away"; + else + bonjour_status = "dnd"; + + bonjour_dns_sd_send_status(bd->dns_sd_data, bonjour_status, stripped); + g_free(stripped); +} + +static GList * +bonjour_status_types(GaimAccount *account) +{ + GList *status_types = NULL; + GaimStatusType *type; + + g_return_val_if_fail(account != NULL, NULL); + + type = gaim_status_type_new_with_attrs(GAIM_STATUS_AVAILABLE, + BONJOUR_STATUS_ID_AVAILABLE, + NULL, TRUE, TRUE, FALSE, + "message", _("Message"), + gaim_value_new(GAIM_TYPE_STRING), NULL); + status_types = g_list_append(status_types, type); + + type = gaim_status_type_new_with_attrs(GAIM_STATUS_AWAY, + BONJOUR_STATUS_ID_AWAY, + NULL, TRUE, TRUE, FALSE, + "message", _("Message"), + gaim_value_new(GAIM_TYPE_STRING), NULL); + status_types = g_list_append(status_types, type); + + type = gaim_status_type_new_full(GAIM_STATUS_OFFLINE, + BONJOUR_STATUS_ID_OFFLINE, + NULL, TRUE, TRUE, FALSE); + status_types = g_list_append(status_types, type); + + return status_types; +} + +static void +bonjour_convo_closed(GaimConnection *connection, const char *who) +{ + GaimBuddy *buddy = gaim_find_buddy(connection->account, who); + + if (buddy == NULL) + { + /* + * This buddy is not in our buddy list, and therefore does not really + * exist, so we won't have any data about them. + */ + return; + } + + bonjour_jabber_close_conversation(((BonjourData*)(connection->proto_data))->jabber_data, buddy); +} + +static void +bonjour_list_emblems(GaimBuddy *buddy, + const char **se, const char **sw, + const char **nw, const char **ne) +{ + GaimPresence *presence; + + presence = gaim_buddy_get_presence(buddy); + + if (gaim_presence_is_online(presence) && !gaim_presence_is_available(presence)) + *se = "away"; +} + +static char * +bonjour_status_text(GaimBuddy *buddy) +{ + GaimPresence *presence; + + presence = gaim_buddy_get_presence(buddy); + + if (gaim_presence_is_online(presence) && !gaim_presence_is_available(presence)) + return g_strdup("Away"); + + return NULL; +} + +static void +bonjour_tooltip_text(GaimBuddy *buddy, GString *str, gboolean full) +{ + GaimPresence *presence; + GaimStatus *status; + const char *status_description; + const char *message; + + presence = gaim_buddy_get_presence(buddy); + status = gaim_presence_get_active_status(presence); + message = gaim_status_get_attr_string(status, "message"); + + if (gaim_presence_is_available(presence)) + status_description = gaim_status_get_name(status); + else if (gaim_presence_is_idle(presence)) + status_description = _("Idle"); + else + status_description = gaim_status_get_name(status); + + g_string_append_printf(str, _("\nStatus: %s"), status_description); + if (message != NULL) + g_string_append_printf(str, _("\nMessage: %s"), message); +} + +static gboolean +plugin_unload(GaimPlugin *plugin) +{ + g_free(default_firstname); + g_free(default_lastname); + g_free(default_hostname); + + return TRUE; +} + +static GaimPlugin *my_protocol = NULL; + +static GaimPluginProtocolInfo prpl_info = +{ + OPT_PROTO_NO_PASSWORD, + NULL, /* user_splits */ + NULL, /* protocol_options */ + /* {"png", 0, 0, 96, 96, GAIM_ICON_SCALE_DISPLAY}, */ /* icon_spec */ + NO_BUDDY_ICONS, /* not yet */ /* icon_spec */ + bonjour_list_icon, /* list_icon */ + bonjour_list_emblems, /* list_emblems */ + bonjour_status_text, /* status_text */ + bonjour_tooltip_text, /* tooltip_text */ + bonjour_status_types, /* status_types */ + NULL, /* blist_node_menu */ + NULL, /* chat_info */ + NULL, /* chat_info_defaults */ + bonjour_login, /* login */ + bonjour_close, /* close */ + bonjour_send_im, /* send_im */ + NULL, /* set_info */ + NULL, /* send_typing */ + NULL, /* get_info */ + bonjour_set_status, /* set_status */ + NULL, /* set_idle */ + NULL, /* change_passwd */ + NULL, /* add_buddy */ + NULL, /* add_buddies */ + NULL, /* remove_buddy */ + NULL, /* remove_buddies */ + NULL, /* add_permit */ + NULL, /* add_deny */ + NULL, /* rem_permit */ + NULL, /* rem_deny */ + NULL, /* set_permit_deny */ + NULL, /* join_chat */ + NULL, /* reject_chat */ + NULL, /* get_chat_name */ + NULL, /* chat_invite */ + NULL, /* chat_leave */ + NULL, /* chat_whisper */ + NULL, /* chat_send */ + NULL, /* keepalive */ + NULL, /* register_user */ + NULL, /* get_cb_info */ + NULL, /* get_cb_away */ + NULL, /* alias_buddy */ + NULL, /* group_buddy */ + NULL, /* rename_group */ + NULL, /* buddy_free */ + bonjour_convo_closed, /* convo_closed */ + NULL, /* normalize */ + NULL, /* set_buddy_icon */ + NULL, /* remove_group */ + NULL, /* get_cb_real_name */ + NULL, /* set_chat_topic */ + NULL, /* find_blist_chat */ + NULL, /* roomlist_get_list */ + NULL, /* roomlist_cancel */ + NULL, /* roomlist_expand_category */ + NULL, /* can_receive_file */ + NULL, /* send_file */ + NULL, /* new_xfer */ + NULL, /* offline_message */ + NULL, /* whiteboard_prpl_ops */ +}; + +static GaimPluginInfo info = +{ + GAIM_PLUGIN_MAGIC, + GAIM_MAJOR_VERSION, + GAIM_MINOR_VERSION, + GAIM_PLUGIN_PROTOCOL, /**< type */ + NULL, /**< ui_requirement */ + 0, /**< flags */ + NULL, /**< dependencies */ + GAIM_PRIORITY_DEFAULT, /**< priority */ + + "prpl-bonjour", /**< id */ + "Bonjour", /**< name */ + VERSION, /**< version */ + /** summary */ + N_("Bonjour Protocol Plugin"), + /** description */ + N_("Bonjour Protocol Plugin"), + NULL, /**< author */ + GAIM_WEBSITE, /**< homepage */ + + NULL, /**< load */ + plugin_unload, /**< unload */ + NULL, /**< destroy */ + + NULL, /**< ui_info */ + &prpl_info, /**< extra_info */ + NULL, /**< prefs_info */ + NULL +}; + +static void +initialize_default_account_values() +{ +#ifdef _WIN32 + char *fullname = NULL; +#else + struct passwd *info; + const char *fullname = NULL; +#endif + char *splitpoint = NULL; + char hostname[255]; + +#ifndef _WIN32 + /* Try to figure out the user's real name */ + info = getpwuid(getuid()); + if ((info != NULL) && (info->pw_gecos != NULL) && (info->pw_gecos[0] != '\0')) + fullname = info->pw_gecos; + else if ((info != NULL) && (info->pw_name != NULL) && (info->pw_name[0] != '\0')) + fullname = info->pw_name; + else if (((fullname = getlogin()) != NULL) && (fullname[0] != '\0')) + ; + else + fullname = _("Gaim User"); + + /* Make sure fullname is valid UTF-8. If not, try to convert it. */ + if (!g_utf8_validate(fullname, -1, NULL)) + { + gchar *tmp; + tmp = g_locale_to_utf8(fullname, -1, NULL, NULL, NULL); + if ((tmp == NULL) || (*tmp == '\0')) + fullname = _("Gaim User"); + } + +#else + FARPROC myNetUserGetInfo = wgaim_find_and_loadproc("Netapi32.dll", + "NetUserGetInfo"); + + if (myNetUserGetInfo) { + LPUSER_INFO_10 user_info = NULL; + LPSERVER_INFO_100 server_info = NULL; + wchar_t *servername = NULL; + wchar_t username[UNLEN + 1] = {'\0'}; + DWORD dwLenUsername = sizeof(username); + FARPROC myNetServerEnum = wgaim_find_and_loadproc( + "Netapi32.dll", "NetServerEnum"); + FARPROC myNetApiBufferFree = wgaim_find_and_loadproc( + "Netapi32.dll", "NetApiBufferFree"); + + if (myNetServerEnum && myNetApiBufferFree) { + DWORD dwEntriesRead = 0; + DWORD dwTotalEntries = 0; + DWORD dwResumeHandle = 0; + + NET_API_STATUS nStatus = (myNetServerEnum)(NULL, 100, + &server_info, MAX_PREFERRED_LENGTH, + &dwEntriesRead, &dwTotalEntries, + SV_TYPE_DOMAIN_CTRL, NULL, &dwResumeHandle); + + if ((nStatus == NERR_Success + || nStatus == ERROR_MORE_DATA) + && dwEntriesRead > 0) { + servername = server_info->sv100_name; + } else { + gaim_debug_warning("bonjour", "Unable to look up domain controller. NET_API_STATUS = %d, Entries Read = %d, Total Entries = %d\n", nStatus, dwEntriesRead, dwTotalEntries); + } + } + + if (!GetUserNameW(&username, &dwLenUsername)) { + gaim_debug_warning("bonjour", + "Unable to look up username\n"); + } + + if (username != NULL && *username != '\0' + && (myNetUserGetInfo)(servername, username, 10, + &user_info) == NERR_Success) { + if (user_info != NULL) { + fullname = g_utf16_to_utf8( + user_info->usri10_full_name, + -1, NULL, NULL, NULL); + } + } + if (user_info != NULL) + (myNetApiBufferFree)(user_info); + if (server_info != NULL) + (myNetApiBufferFree)(server_info); + } + + if (!fullname) + fullname = g_strdup(_("Gaim User")); +#endif + + /* Split the real name into a first and last name */ + splitpoint = strchr(fullname, ' '); + if (splitpoint != NULL) + { + default_firstname = g_strndup(fullname, splitpoint - fullname); + default_lastname = g_strdup(&splitpoint[1]); + } + else + { + default_firstname = g_strdup(fullname); + default_lastname = g_strdup(""); + } + +#ifdef _WIN32 + g_free(fullname); +#endif + + /* Try to figure out a good host name to use */ + /* TODO: Avoid 'localhost,' if possible */ + if (gethostname(hostname, 255) != 0) { + gaim_debug_warning("bonjour", "Error %d when getting host name. Using \"localhost.\"\n", errno); + strcpy(hostname, "localhost"); + } + default_hostname = g_strdup(hostname); +} + +static void +init_plugin(GaimPlugin *plugin) +{ + GaimAccountUserSplit *split; + GaimAccountOption *option; + + initialize_default_account_values(); + + /* Creating the user splits */ + split = gaim_account_user_split_new(_("Hostname"), default_hostname, '@'); + prpl_info.user_splits = g_list_append(prpl_info.user_splits, split); + + /* Creating the options for the protocol */ + option = gaim_account_option_string_new(_("First name"), "first", default_firstname); + prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); + + option = gaim_account_option_string_new(_("Last name"), "last", default_lastname); + prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); + + option = gaim_account_option_string_new(_("E-mail"), "email", ""); + prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); + + option = gaim_account_option_string_new(_("AIM Account"), "AIM", ""); + prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); + + option = gaim_account_option_string_new(_("Jabber Account"), "jid", ""); + prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); + + my_protocol = plugin; +} + +GAIM_INIT_PLUGIN(bonjour, init_plugin, info); diff -r d10dda2777a9 -r b63ebf84c42b core/protocols/bonjour/bonjour.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/protocols/bonjour/bonjour.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,51 @@ +/** + * @file bonjour.h The Gaim interface to mDNS and peer to peer Jabber. + * + * 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 + * + */ + +#ifndef _BONJOUR_H_ +#define _BONJOUR_H_ + +#include + +#include "dns_sd.h" +#include "internal.h" +#include "jabber.h" + +#define BONJOUR_GROUP_NAME _("Bonjour") +#define BONJOUR_PROTOCOL_NAME "bonjour" +#define BONJOUR_ICON_NAME "bonjour" + +#define BONJOUR_STATUS_ID_OFFLINE "offline" +#define BONJOUR_STATUS_ID_AVAILABLE "available" +#define BONJOUR_STATUS_ID_AWAY "away" + +#define BONJOUR_DEFAULT_PORT_INT 5298 + +typedef struct _BonjourData +{ + BonjourDnsSd *dns_sd_data; + BonjourJabber *jabber_data; +} BonjourData; + +#endif /* _BONJOUR_H_ */ diff -r d10dda2777a9 -r b63ebf84c42b core/protocols/bonjour/buddy.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/protocols/bonjour/buddy.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,167 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include + +#include "buddy.h" +#include "account.h" +#include "blist.h" +#include "bonjour.h" +#include "debug.h" + +/** + * Creates a new buddy. + */ +BonjourBuddy * +bonjour_buddy_new(const gchar *name, const gchar *first, gint port_p2pj, + const gchar *phsh, const gchar *status, const gchar *email, + const gchar *last, const gchar *jid, const gchar *AIM, + const gchar *vc, const gchar *ip, const gchar *msg) +{ + BonjourBuddy *buddy = malloc(sizeof(BonjourBuddy)); + + buddy->name = g_strdup(name); + buddy->first = g_strdup(first); + buddy->port_p2pj = port_p2pj; + buddy->phsh = g_strdup(phsh); + buddy->status = g_strdup(status); + buddy->email = g_strdup(email); + buddy->last = g_strdup(last); + buddy->jid = g_strdup(jid); + buddy->AIM = g_strdup(AIM); + buddy->vc = g_strdup(vc); + buddy->ip = g_strdup(ip); + buddy->msg = g_strdup(msg); + buddy->conversation = NULL; + + return buddy; +} + +/** + * Check if all the compulsory buddy data is present. + */ +gboolean +bonjour_buddy_check(BonjourBuddy *buddy) +{ + if (buddy->name == NULL) { + return FALSE; + } + + if (buddy->first == NULL) { + return FALSE; + } + + if (buddy->last == NULL) { + return FALSE; + } + + if (buddy->status == NULL) { + return FALSE; + } + + return TRUE; +} + +/** + * If the buddy does not yet exist, then create it and add it to + * our buddy list. In either case we set the correct status for + * the buddy. + */ +void +bonjour_buddy_add_to_gaim(GaimAccount *account, BonjourBuddy *bonjour_buddy) +{ + GaimBuddy *buddy; + GaimGroup *group; + const char *status_id, *first, *last; + char *alias; + + /* Translate between the Bonjour status and the Gaim status */ + if (g_ascii_strcasecmp("dnd", bonjour_buddy->status) == 0) + status_id = BONJOUR_STATUS_ID_AWAY; + else + status_id = BONJOUR_STATUS_ID_AVAILABLE; + + /* + * TODO: Figure out the idle time by getting the "away" + * field from the DNS SD. + */ + + /* Create the alias for the buddy using the first and the last name */ + first = bonjour_buddy->first; + last = bonjour_buddy->last; + alias = g_strdup_printf("%s%s%s", + (first && *first ? first : ""), + (first && *first && last && *last ? " " : ""), + (last && *last ? last : "")); + + /* Make sure the Bonjour group exists in our buddy list */ + group = gaim_find_group(BONJOUR_GROUP_NAME); /* Use the buddy's domain, instead? */ + if (group == NULL) + { + group = gaim_group_new(BONJOUR_GROUP_NAME); + gaim_blist_add_group(group, NULL); + } + + /* Make sure the buddy exists in our buddy list */ + buddy = gaim_find_buddy(account, bonjour_buddy->name); + if (buddy == NULL) + { + buddy = gaim_buddy_new(account, bonjour_buddy->name, alias); + buddy->proto_data = bonjour_buddy; + gaim_blist_node_set_flags((GaimBlistNode *)buddy, GAIM_BLIST_NODE_FLAG_NO_SAVE); + gaim_blist_add_buddy(buddy, NULL, group, NULL); + } + + /* Set the user's status */ + if (bonjour_buddy->msg != NULL) + gaim_prpl_got_user_status(account, buddy->name, status_id, + "message", bonjour_buddy->msg, + NULL); + else + gaim_prpl_got_user_status(account, buddy->name, status_id, + NULL); + gaim_prpl_got_user_idle(account, buddy->name, FALSE, 0); + + g_free(alias); +} + +/** + * Deletes a buddy from memory. + */ +void +bonjour_buddy_delete(BonjourBuddy *buddy) +{ + g_free(buddy->name); + g_free(buddy->first); + g_free(buddy->phsh); + g_free(buddy->status); + g_free(buddy->email); + g_free(buddy->last); + g_free(buddy->jid); + g_free(buddy->AIM); + g_free(buddy->vc); + g_free(buddy->ip); + g_free(buddy->msg); + + if (buddy->conversation != NULL) + { + g_free(buddy->conversation->buddy_name); + g_free(buddy->conversation); + } + + free(buddy); +} diff -r d10dda2777a9 -r b63ebf84c42b core/protocols/bonjour/buddy.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/protocols/bonjour/buddy.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,66 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _BONJOUR_BUDDY +#define _BONJOUR_BUDDY + +#include +#include + +#include "account.h" +#include "jabber.h" + +typedef struct _BonjourBuddy +{ + gchar *name; + gchar *first; + gint port_p2pj; + gchar *phsh; + gchar *status; + gchar *email; + gchar *last; + gchar *jid; + gchar *AIM; + gchar *vc; + gchar *ip; + gchar *msg; + BonjourJabberConversation *conversation; +} BonjourBuddy; + +/** + * Creates a new buddy. + */ +BonjourBuddy *bonjour_buddy_new(const gchar *name, const gchar *first, + gint port_p2pj, const gchar *phsh, const gchar *status, + const gchar *email, const gchar *last, const gchar *jid, + const gchar *AIM, const gchar *vc, const gchar *ip, const gchar *msg); + +/** + * Check if all the compulsory buddy data is present. + */ +gboolean bonjour_buddy_check(BonjourBuddy *buddy); + +/** + * If the buddy doesn't previoulsy exists, it is created. Else, its data is changed (???) + */ +void bonjour_buddy_add_to_gaim(GaimAccount *account, BonjourBuddy *buddy); + +/** + * Deletes a buddy from memory. + */ +void bonjour_buddy_delete(BonjourBuddy *buddy); + +#endif diff -r d10dda2777a9 -r b63ebf84c42b core/protocols/bonjour/dns_sd.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/protocols/bonjour/dns_sd.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,387 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include + +#include "dns_sd.h" +#include "bonjour.h" +#include "buddy.h" +#include "debug.h" + +/* Private functions */ + +static sw_result HOWL_API +_publish_reply(sw_discovery discovery, sw_discovery_oid oid, + sw_discovery_publish_status status, sw_opaque extra) +{ + gaim_debug_warning("bonjour", "_publish_reply --> Start\n"); + + /* Check the answer from the mDNS daemon */ + switch (status) + { + case SW_DISCOVERY_PUBLISH_STARTED : + gaim_debug_info("bonjour", "_publish_reply --> Service started\n"); + break; + case SW_DISCOVERY_PUBLISH_STOPPED : + gaim_debug_info("bonjour", "_publish_reply --> Service stopped\n"); + break; + case SW_DISCOVERY_PUBLISH_NAME_COLLISION : + gaim_debug_info("bonjour", "_publish_reply --> Name collision\n"); + break; + case SW_DISCOVERY_PUBLISH_INVALID : + gaim_debug_info("bonjour", "_publish_reply --> Service invalid\n"); + break; + } + + return SW_OKAY; +} + +static sw_result HOWL_API +_resolve_reply(sw_discovery discovery, sw_discovery_oid oid, + sw_uint32 interface_index, sw_const_string name, + sw_const_string type, sw_const_string domain, + sw_ipv4_address address, sw_port port, + sw_octets text_record, sw_ulong text_record_len, + sw_opaque extra) +{ + BonjourBuddy *buddy; + GaimAccount *account = (GaimAccount*)extra; + gchar *txtvers = NULL; + gchar *version = NULL; + gchar *first = NULL; + gchar *phsh = NULL; + gchar *status = NULL; + gchar *email = NULL; + gchar *last = NULL; + gchar *jid = NULL; + gchar *AIM = NULL; + gchar *vc = NULL; + gchar *msg = NULL; + gint address_length = 16; + gchar *ip = NULL; + sw_text_record_iterator iterator; + char key[SW_TEXT_RECORD_MAX_LEN]; + char value[SW_TEXT_RECORD_MAX_LEN]; + sw_uint32 value_length; + + sw_discovery_cancel(discovery, oid); + + /* Get the ip as a string */ + ip = malloc(address_length); + sw_ipv4_address_name(address, ip, address_length); + + /* Obtain the parameters from the text_record */ + if ((text_record_len > 0) && (text_record) && (*text_record != '\0')) + { + sw_text_record_iterator_init(&iterator, text_record, text_record_len); + while (sw_text_record_iterator_next(iterator, key, (sw_octet *)value, &value_length) == SW_OKAY) + { + /* Compare the keys with the possible ones and save them on */ + /* the appropiate place of the buddy_list */ + if (strcmp(key, "txtvers") == 0) { + txtvers = g_strdup(value); + } else if (strcmp(key, "version") == 0) { + version = g_strdup(value); + } else if (strcmp(key, "1st") == 0) { + first = g_strdup(value); + } else if (strcmp(key, "status") == 0) { + status = g_strdup(value); + } else if (strcmp(key, "email") == 0) { + email = g_strdup(value); + } else if (strcmp(key, "last") == 0) { + last = g_strdup(value); + } else if (strcmp(key, "jid") == 0) { + jid = g_strdup(value); + } else if (strcmp(key, "AIM") == 0) { + AIM = g_strdup(value); + } else if (strcmp(key, "vc") == 0) { + vc = g_strdup(value); + } else if (strcmp(key, "phsh") == 0) { + phsh = g_strdup(value); + } else if (strcmp(key, "msg") == 0) { + msg = g_strdup(value); + } + } + } + + /* Put the parameters of the text_record in a buddy and add the buddy to */ + /* the buddy list */ + buddy = bonjour_buddy_new(name, first, port, phsh, + status, email, last, jid, AIM, vc, ip, msg); + + if (bonjour_buddy_check(buddy) == FALSE) + { + bonjour_buddy_delete(buddy); + return SW_DISCOVERY_E_UNKNOWN; + } + + /* Add or update the buddy in our buddy list */ + bonjour_buddy_add_to_gaim(account, buddy); + + /* Free all the temporal strings */ + g_free(txtvers); + g_free(version); + g_free(first); + g_free(last); + g_free(status); + g_free(email); + g_free(jid); + g_free(AIM); + g_free(vc); + g_free(phsh); + g_free(msg); + + return SW_OKAY; +} + +static sw_result HOWL_API +_browser_reply(sw_discovery discovery, sw_discovery_oid oid, + sw_discovery_browse_status status, + sw_uint32 interface_index, sw_const_string name, + sw_const_string type, sw_const_string domain, + sw_opaque_t extra) +{ + sw_discovery_resolve_id rid; + GaimAccount *account = (GaimAccount*)extra; + GaimBuddy *gb = NULL; + + switch (status) + { + case SW_DISCOVERY_BROWSE_INVALID: + gaim_debug_warning("bonjour", "_browser_reply --> Invalid\n"); + break; + case SW_DISCOVERY_BROWSE_RELEASE: + gaim_debug_warning("bonjour", "_browser_reply --> Release\n"); + break; + case SW_DISCOVERY_BROWSE_ADD_DOMAIN: + gaim_debug_warning("bonjour", "_browser_reply --> Add domain\n"); + break; + case SW_DISCOVERY_BROWSE_ADD_DEFAULT_DOMAIN: + gaim_debug_warning("bonjour", "_browser_reply --> Add default domain\n"); + break; + case SW_DISCOVERY_BROWSE_REMOVE_DOMAIN: + gaim_debug_warning("bonjour", "_browser_reply --> Remove domain\n"); + break; + case SW_DISCOVERY_BROWSE_ADD_SERVICE: + /* A new peer has joined the network and uses iChat bonjour */ + gaim_debug_info("bonjour", "_browser_reply --> Add service\n"); + if (g_ascii_strcasecmp(name, account->username) != 0) + { + if (sw_discovery_resolve(discovery, interface_index, name, type, + domain, _resolve_reply, extra, &rid) != SW_OKAY) + { + gaim_debug_warning("bonjour", "_browser_reply --> Cannot send resolve\n"); + } + } + break; + case SW_DISCOVERY_BROWSE_REMOVE_SERVICE: + gaim_debug_info("bonjour", "_browser_reply --> Remove service\n"); + gb = gaim_find_buddy((GaimAccount*)extra, name); + if (gb != NULL) + { + bonjour_buddy_delete(gb->proto_data); + gaim_blist_remove_buddy(gb); + } + break; + case SW_DISCOVERY_BROWSE_RESOLVED: + gaim_debug_info("bonjour", "_browse_reply --> Resolved\n"); + break; + default: + break; + } + + return SW_OKAY; +} + +static int +_dns_sd_publish(BonjourDnsSd *data, PublishType type) +{ + sw_text_record dns_data; + sw_result publish_result = SW_OKAY; + char portstring[6]; + + /* Fill the data for the service */ + if (sw_text_record_init(&dns_data) != SW_OKAY) + { + gaim_debug_error("bonjour", "Unable to initialize the data for the mDNS.\n"); + return -1; + } + + /* Convert the port to a string */ + snprintf(portstring, sizeof(portstring), "%d", data->port_p2pj); + + /* Publish standard records */ + sw_text_record_add_key_and_string_value(dns_data, "txtvers", data->txtvers); + sw_text_record_add_key_and_string_value(dns_data, "version", data->version); + sw_text_record_add_key_and_string_value(dns_data, "1st", data->first); + sw_text_record_add_key_and_string_value(dns_data, "last", data->last); + sw_text_record_add_key_and_string_value(dns_data, "port.p2pj", portstring); + sw_text_record_add_key_and_string_value(dns_data, "phsh", data->phsh); + sw_text_record_add_key_and_string_value(dns_data, "status", data->status); + sw_text_record_add_key_and_string_value(dns_data, "vc", data->vc); + + /* Publish extra records */ + if ((data->email != NULL) && (*data->email != '\0')) + sw_text_record_add_key_and_string_value(dns_data, "email", data->email); + + if ((data->jid != NULL) && (*data->jid != '\0')) + sw_text_record_add_key_and_string_value(dns_data, "jid", data->jid); + + if ((data->AIM != NULL) && (*data->AIM != '\0')) + sw_text_record_add_key_and_string_value(dns_data, "AIM", data->AIM); + + if ((data->msg != NULL) && (*data->msg != '\0')) + sw_text_record_add_key_and_string_value(dns_data, "msg", data->msg); + + /* Publish the service */ + switch (type) + { + case PUBLISH_START: + publish_result = sw_discovery_publish(data->session, 0, data->name, ICHAT_SERVICE, NULL, + NULL, data->port_p2pj, sw_text_record_bytes(dns_data), sw_text_record_len(dns_data), + _publish_reply, NULL, &data->session_id); + break; + case PUBLISH_UPDATE: + publish_result = sw_discovery_publish_update(data->session, data->session_id, + sw_text_record_bytes(dns_data), sw_text_record_len(dns_data)); + break; + } + if (publish_result != SW_OKAY) + { + gaim_debug_error("bonjour", "Unable to publish or change the status of the _presence._tcp service.\n"); + return -1; + } + + /* Free the memory used by temp data */ + sw_text_record_fina(dns_data); + + return 0; +} + +static void +_dns_sd_handle_packets(gpointer data, gint source, GaimInputCondition condition) +{ + sw_discovery_read_socket((sw_discovery)data); +} + +/* End private functions */ + +/** + * Allocate space for the dns-sd data. + */ +BonjourDnsSd * +bonjour_dns_sd_new() +{ + BonjourDnsSd *data = g_new0(BonjourDnsSd, 1); + + return data; +} + +/** + * Deallocate the space of the dns-sd data. + */ +void +bonjour_dns_sd_free(BonjourDnsSd *data) +{ + g_free(data->first); + g_free(data->last); + g_free(data->email); + g_free(data); +} + +/** + * Send a new dns-sd packet updating our status. + */ +void +bonjour_dns_sd_send_status(BonjourDnsSd *data, const char *status, const char *status_message) +{ + g_free(data->status); + g_free(data->msg); + + data->status = g_strdup(status); + data->msg = g_strdup(status_message); + + /* Update our text record with the new status */ + _dns_sd_publish(data, PUBLISH_UPDATE); /* <--We must control the errors */ +} + +/** + * Advertise our presence within the dns-sd daemon and start browsing + * for other bonjour peers. + */ +gboolean +bonjour_dns_sd_start(BonjourDnsSd *data) +{ + GaimAccount *account; + GaimConnection *gc; + gint dns_sd_socket; + sw_discovery_oid session_id; + + account = data->account; + gc = gaim_account_get_connection(account); + + /* Initialize the dns-sd data and session */ + if (sw_discovery_init(&data->session) != SW_OKAY) + { + gaim_debug_error("bonjour", "Unable to initialize an mDNS session.\n"); + + /* In Avahi, sw_discovery_init frees data->session but doesn't clear it */ + data->session = NULL; + + return FALSE; + } + + /* Publish our bonjour IM client at the mDNS daemon */ + _dns_sd_publish(data, PUBLISH_START); /* <--We must control the errors */ + + /* Advise the daemon that we are waiting for connections */ + if (sw_discovery_browse(data->session, 0, ICHAT_SERVICE, NULL, _browser_reply, + data->account, &session_id) != SW_OKAY) + { + gaim_debug_error("bonjour", "Unable to get service."); + return FALSE; + } + + /* Get the socket that communicates with the mDNS daemon and bind it to a */ + /* callback that will handle the dns_sd packets */ + dns_sd_socket = sw_discovery_socket(data->session); + gc->inpa = gaim_input_add(dns_sd_socket, GAIM_INPUT_READ, + _dns_sd_handle_packets, data->session); + + return TRUE; +} + +/** + * Unregister the "_presence._tcp" service at the mDNS daemon. + */ +void +bonjour_dns_sd_stop(BonjourDnsSd *data) +{ + GaimAccount *account; + GaimConnection *gc; + + if (data->session == NULL) + return; + + sw_discovery_cancel(data->session, data->session_id); + + account = data->account; + gc = gaim_account_get_connection(account); + gaim_input_remove(gc->inpa); + + g_free(data->session); + data->session = NULL; +} diff -r d10dda2777a9 -r b63ebf84c42b core/protocols/bonjour/dns_sd.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/protocols/bonjour/dns_sd.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,81 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _BONJOUR_DNS_SD +#define _BONJOUR_DNS_SD + +#include +#include +#include "account.h" + +#define ICHAT_SERVICE "_presence._tcp." + +/** + * Data to be used by the dns-sd connection. + */ +typedef struct _BonjourDnsSd +{ + sw_discovery session; + sw_discovery_oid session_id; + GaimAccount *account; + gchar *name; + gchar *txtvers; + gchar *version; + gchar *first; + gchar *last; + gint port_p2pj; + gchar *phsh; + gchar *status; + gchar *email; + gchar *vc; + gchar *jid; + gchar *AIM; + gchar *msg; + GHashTable *buddies; +} BonjourDnsSd; + +typedef enum _PublishType { + PUBLISH_START, + PUBLISH_UPDATE +} PublishType; + +/** + * Allocate space for the dns-sd data. + */ +BonjourDnsSd *bonjour_dns_sd_new(void); + +/** + * Deallocate the space of the dns-sd data. + */ +void bonjour_dns_sd_free(BonjourDnsSd *data); + +/** + * Send a new dns-sd packet updating our status. + */ +void bonjour_dns_sd_send_status(BonjourDnsSd *data, const char *status, const char *status_message); + +/** + * Advertise our presence within the dns-sd daemon and start + * browsing for other bonjour peers. + */ +gboolean bonjour_dns_sd_start(BonjourDnsSd *data); + +/** + * Unregister the "_presence._tcp" service at the mDNS daemon. + */ +void bonjour_dns_sd_stop(BonjourDnsSd *data); + +#endif diff -r d10dda2777a9 -r b63ebf84c42b core/protocols/bonjour/issues.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/protocols/bonjour/issues.txt Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,18 @@ +========================================== +============= Known issues =============== +========================================== + +(1) Messages are limited in length (5000 char) <-- FIXED +(2) Messages formated by Gaim didn't work <-- FIXED +(3) iChat sends the size in points, Gaim wants a 1..7 range <-- FIXED Gaim2iChat (iChat2Gaim left) +(4) When the other end closes the socket without sending the end of stream, Gaim crashes and coredump <-- FIXED +(5) I18n +(6) Status changes don't work +(7) When the conversation is closed in Gaim with the X button, we don't send the end of stream <-- FIXED +(8) The server socket is not reusable, after an error, you cannot connect for a while <-- FIXED +(9) Avatars +(10) File transfers +(11) Typing notifications +(12) Gaim HTML syntax is not shown properly <-- FIXED +(13) Strange messages creates coredump <-- FIXED +(14) Check if it works on win32 diff -r d10dda2777a9 -r b63ebf84c42b core/protocols/bonjour/jabber.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/protocols/bonjour/jabber.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,667 @@ +/* + * gaim - Bonjour Protocol Plugin + * + * 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 + */ +#ifndef _WIN32 +#include +#include +#include +#else +#include "libc_interface.h" +#endif +#include +#include +#include +#include +#include + +#include "network.h" +#include "eventloop.h" +#include "connection.h" +#include "blist.h" +#include "xmlnode.h" +#include "debug.h" +#include "notify.h" +#include "util.h" + +#include "jabber.h" +#include "bonjour.h" +#include "buddy.h" + +static gint +_connect_to_buddy(GaimBuddy *gb) +{ + gint socket_fd; + gint retorno = 0; + struct sockaddr_in buddy_address; + + /* Create a socket and make it non-blocking */ + socket_fd = socket(PF_INET, SOCK_STREAM, 0); + + buddy_address.sin_family = PF_INET; + buddy_address.sin_port = htons(((BonjourBuddy*)(gb->proto_data))->port_p2pj); + inet_aton(((BonjourBuddy*)(gb->proto_data))->ip, &(buddy_address.sin_addr)); + memset(&(buddy_address.sin_zero), '\0', 8); + + retorno = connect(socket_fd, (struct sockaddr*)&buddy_address, sizeof(struct sockaddr)); + if (retorno == -1) { + gaim_debug_warning("bonjour", "connect error: %s\n", strerror(errno)); + } + fcntl(socket_fd, F_SETFL, O_NONBLOCK); + + return socket_fd; +} + +#if 0 /* this isn't used anywhere... */ +static const char * +_font_size_gaim_to_ichat(int size) +{ + switch (size) { + case 1: + return "8"; + case 2: + return "10"; + case 3: + return "12"; + case 4: + return "14"; + case 5: + return "17"; + case 6: + return "21"; + case 7: + return "24"; + } + + return "12"; +} +#endif + +static const char * +_font_size_ichat_to_gaim(int size) +{ + if (size > 24) { + return "7"; + } else if (size >= 21) { + return "6"; + } else if (size >= 17) { + return "5"; + } else if (size >= 14) { + return "4"; + } else if (size >= 12) { + return "3"; + } else if (size >= 10) { + return "2"; + } + + return "1"; +} +static void +_jabber_parse_and_write_message_to_ui(char *message, GaimConnection *connection, GaimBuddy *gb) +{ + xmlnode *body_node = NULL; + char *body = NULL; + xmlnode *html_node = NULL; + gboolean isHTML = FALSE; + xmlnode *html_body_node = NULL; + const char *ichat_balloon_color = NULL; + const char *ichat_text_color = NULL; + xmlnode *html_body_font_node = NULL; + const char *font_face = NULL; + const char *font_size = NULL; + const char *font_color = NULL; + char *html_body = NULL; + xmlnode *events_node = NULL; + gboolean composing_event = FALSE; + gint garbage = -1; + xmlnode *message_node = NULL; + + /* Parsing of the message */ + message_node = xmlnode_from_str(message, strlen(message)); + if (message_node == NULL) { + return; + } + + body_node = xmlnode_get_child(message_node, "body"); + if (body_node != NULL) { + body = xmlnode_get_data(body_node); + } else { + return; + } + + html_node = xmlnode_get_child(message_node, "html"); + if (html_node != NULL) + { + isHTML = TRUE; + html_body_node = xmlnode_get_child(html_node, "body"); + if (html_body_node != NULL) + { + ichat_balloon_color = xmlnode_get_attrib(html_body_node, "ichatballooncolor"); + ichat_text_color = xmlnode_get_attrib(html_body_node, "ichattextcolor"); + html_body_font_node = xmlnode_get_child(html_body_node, "font"); + if (html_body_font_node != NULL) + { /* Types of messages sent by iChat */ + font_face = xmlnode_get_attrib(html_body_font_node, "face"); + /* The absolute iChat font sizes should be converted to 1..7 range */ + font_size = xmlnode_get_attrib(html_body_font_node, "ABSZ"); + if (font_size != NULL) + { + font_size = _font_size_ichat_to_gaim(atoi(font_size)); + } + font_color = xmlnode_get_attrib(html_body_font_node, "color"); + html_body = xmlnode_get_data(html_body_font_node); + if (html_body == NULL) + { + /* This is the kind of formated messages that Gaim creates */ + html_body = xmlnode_to_str(html_body_font_node, &garbage); + } + } else { + isHTML = FALSE; + } + } else { + isHTML = FALSE; + } + + } + + events_node = xmlnode_get_child_with_namespace(message_node, "x", "jabber:x:event"); + if (events_node != NULL) + { + if (xmlnode_get_child(events_node, "composing") != NULL) + { + composing_event = TRUE; + } + if (xmlnode_get_child(events_node, "id") != NULL) + { + /* The user is just typing */ + xmlnode_free(message_node); + g_free(body); + g_free(html_body); + return; + } + } + + /* Compose the message */ + if (isHTML) + { + if (font_face == NULL) font_face = "Helvetica"; + if (font_size == NULL) font_size = "3"; + if (ichat_text_color == NULL) ichat_text_color = "#000000"; + if (ichat_balloon_color == NULL) ichat_balloon_color = "#FFFFFF"; + body = g_strconcat("", html_body, "", NULL); + } + + /* Send the message to the UI */ + serv_got_im(connection, gb->name, body, 0, time(NULL)); + + /* Free all the strings and nodes (the attributes are freed with their nodes) */ + xmlnode_free(message_node); + g_free(body); + g_free(html_body); +} + +struct _check_buddy_by_address_t { + char *address; + GaimBuddy **gb; + BonjourJabber *bj; +}; + +static void +_check_buddy_by_address(gpointer key, gpointer value, gpointer data) +{ + GaimBuddy *gb = (GaimBuddy*)value; + BonjourBuddy *bb; + struct _check_buddy_by_address_t *cbba; + + gb = value; + cbba = data; + + /* + * If the current GaimBuddy's data is not null and the GaimBuddy's account + * is the same as the account requesting the check then continue to determine + * whether the buddies IP matches the target IP. + */ + if (cbba->bj->account == gb->account) + { + bb = gb->proto_data; + if ((bb != NULL) && (g_strcasecmp(bb->ip, cbba->address) == 0)) + *(cbba->gb) = gb; + } +} + +static gint +_read_data(gint socket, char **message) +{ + GString *data = g_string_new(""); + char partial_data[512]; + gint total_message_length = 0; + gint partial_message_length = 0; + + /* Read chunks of 512 bytes till the end of the data */ + while ((partial_message_length = recv(socket, partial_data, 512, 0)) > 0) + { + g_string_append_len(data, partial_data, partial_message_length); + total_message_length += partial_message_length; + } + + if (partial_message_length == -1) + { + gaim_debug_warning("bonjour", "receive error: %s\n", strerror(errno)); + if (total_message_length == 0) { + return -1; + } + } + + *message = data->str; + g_string_free(data, FALSE); + if (total_message_length != 0) + gaim_debug_info("bonjour", "Receive: -%s- %d bytes\n", *message, total_message_length); + + return total_message_length; +} + +static gint +_send_data(gint socket, char *message) +{ + gint message_len = strlen(message); + gint partial_sent = 0; + gchar *partial_message = message; + + while ((partial_sent = send(socket, partial_message, message_len, 0)) < message_len) + { + if (partial_sent != -1) { + partial_message += partial_sent; + message_len -= partial_sent; + } else { + return -1; + } + } + + return strlen(message); +} + +static void +_client_socket_handler(gpointer data, gint socket, GaimInputCondition condition) +{ + char *message = NULL; + gint message_length; + GaimBuddy *gb = (GaimBuddy*)data; + GaimAccount *account = gb->account; + GaimConversation *conversation; + char *closed_conv_message; + BonjourBuddy *bb = (BonjourBuddy*)gb->proto_data; + gboolean closed_conversation = FALSE; + xmlnode *message_node = NULL; + + /* Read the data from the socket */ + if ((message_length = _read_data(socket, &message)) == -1) { + /* There have been an error reading from the socket */ + return; + } else if (message_length == 0) { /* The other end has closed the socket */ + closed_conversation = TRUE; + } else { + message[message_length] = '\0'; + + while (g_ascii_iscntrl(message[message_length - 1])) { + message[message_length - 1] = '\0'; + message_length--; + } + } + + /* Parse the message into an XMLnode for analysis */ + message_node = xmlnode_from_str(message, strlen(message)); + + /* Check if the start of the stream has been received, if not check that the current */ + /* data is the start of the stream */ + if (!(bb->conversation->stream_started)) + { + /* Check if this is the start of the stream */ + if ((message_node != NULL) && + g_ascii_strcasecmp(xmlnode_get_attrib(message_node, "xmlns"), "jabber:client") && + (xmlnode_get_attrib(message_node,"xmlns:stream") != NULL)) + { + bb->conversation->stream_started = TRUE; + } + else + { + /* TODO: This needs to be nonblocking! */ + if (send(bb->conversation->socket, DOCTYPE, strlen(DOCTYPE), 0) == -1) + { + gaim_debug_error("bonjour", "Unable to start a conversation with %s\n", bb->name); + } + else + { + bb->conversation->stream_started = TRUE; + } + } + } + + /* + * Check that this is not the end of the conversation. This is + * using a magic string, but xmlnode won't play nice when just + * parsing an end tag + */ + if (gaim_str_has_prefix(message, STREAM_END) || (closed_conversation == TRUE)) { + /* Close the socket, clear the watcher and free memory */ + if (bb->conversation != NULL) { + close(bb->conversation->socket); + gaim_input_remove(bb->conversation->watcher_id); + g_free(bb->conversation->buddy_name); + g_free(bb->conversation); + bb->conversation = NULL; + } + + /* Inform the user that the conversation has been closed */ + conversation = gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM, gb->name, account); + closed_conv_message = g_strdup_printf(_("%s has closed the conversation."), gb->name); + gaim_conversation_write(conversation, NULL, closed_conv_message, GAIM_MESSAGE_SYSTEM, time(NULL)); + g_free(closed_conv_message); + } else { + /* Parse the message to get the data and send to the ui */ + _jabber_parse_and_write_message_to_ui(message, account->gc, gb); + } + + if (message_node != NULL) + xmlnode_free(message_node); +} + +static void +_server_socket_handler(gpointer data, int server_socket, GaimInputCondition condition) +{ + GaimBuddy *gb = NULL; + struct sockaddr_in their_addr; /* connector's address information */ + socklen_t sin_size = sizeof(struct sockaddr); + int client_socket; + BonjourBuddy *bb = NULL; + BonjourJabber *bj = data; + char *address_text = NULL; + GaimBuddyList *bl = gaim_get_blist(); + struct _check_buddy_by_address_t *cbba; + + /* Check that it is a read condition */ + if (condition != GAIM_INPUT_READ) { + return; + } + + if ((client_socket = accept(server_socket, (struct sockaddr *)&their_addr, &sin_size)) == -1) + { + return; + } + fcntl(client_socket, F_SETFL, O_NONBLOCK); + + /* Look for the buddy that has opened the conversation and fill information */ + address_text = inet_ntoa(their_addr.sin_addr); + cbba = g_new0(struct _check_buddy_by_address_t, 1); + cbba->address = address_text; + cbba->gb = &gb; + cbba->bj = bj; + g_hash_table_foreach(bl->buddies, _check_buddy_by_address, cbba); + g_free(cbba); + if (gb == NULL) + { + gaim_debug_info("bonjour", "We don't like invisible buddies, this is not a superheros comic\n"); + close(client_socket); + return; + } + bb = (BonjourBuddy*)gb->proto_data; + + /* Check if the conversation has been previously started */ + if (bb->conversation == NULL) + { + bb->conversation = g_new(BonjourJabberConversation, 1); + bb->conversation->socket = client_socket; + bb->conversation->stream_started = FALSE; + bb->conversation->buddy_name = g_strdup(gb->name); + bb->conversation->message_id = 1; + + if (bb->conversation->stream_started == FALSE) { + /* Start the stream */ + send(bb->conversation->socket, DOCTYPE, strlen(DOCTYPE), 0); + bb->conversation->stream_started = TRUE; + } + + /* Open a watcher for the client socket */ + bb->conversation->watcher_id = gaim_input_add(client_socket, GAIM_INPUT_READ, + _client_socket_handler, gb); + } else { + close(client_socket); + } +} + +gint +bonjour_jabber_start(BonjourJabber *data) +{ + struct sockaddr_in my_addr; + int yes = 1; + int i; + gboolean bind_successful; + + /* Open a listening socket for incoming conversations */ + if ((data->socket = socket(PF_INET, SOCK_STREAM, 0)) < 0) + { + gaim_debug_error("bonjour", "Cannot open socket: %s\n", strerror(errno)); + gaim_connection_error(data->account->gc, _("Cannot open socket")); + return -1; + } + + /* Make the socket reusable */ + if (setsockopt(data->socket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) != 0) + { + gaim_debug_error("bonjour", "Error setting socket options: %s\n", strerror(errno)); + gaim_connection_error(data->account->gc, _("Error setting socket options")); + return -1; + } + + memset(&my_addr, 0, sizeof(struct sockaddr_in)); + my_addr.sin_family = PF_INET; + + /* Attempt to find a free port */ + bind_successful = FALSE; + for (i = 0; i < 10; i++) + { + my_addr.sin_port = htons(data->port); + if (bind(data->socket, (struct sockaddr*)&my_addr, sizeof(struct sockaddr)) == 0) + { + bind_successful = TRUE; + break; + } + data->port++; + } + + /* On no! We tried 10 ports and could not bind to ANY of them */ + if (!bind_successful) + { + gaim_debug_error("bonjour", "Cannot bind socket: %s\n", strerror(errno)); + gaim_connection_error(data->account->gc, _("Could not bind socket to port")); + return -1; + } + + /* Attempt to listen on the bound socket */ + if (listen(data->socket, 10) != 0) + { + gaim_debug_error("bonjour", "Cannot listen on socket: %s\n", strerror(errno)); + gaim_connection_error(data->account->gc, _("Could not listen on socket")); + return -1; + } + +#if 0 + /* TODO: Why isn't this being used? */ + data->socket = gaim_network_listen(data->port, SOCK_STREAM); + + if (data->socket == -1) + { + gaim_debug_error("bonjour", "No se ha podido crear el socket\n"); + } +#endif + + /* Open a watcher in the socket we have just opened */ + data->watcher_id = gaim_input_add(data->socket, GAIM_INPUT_READ, _server_socket_handler, data); + + return data->port; +} + +int +bonjour_jabber_send_message(BonjourJabber *data, const gchar *to, const gchar *body) +{ + xmlnode *message_node = NULL; + gchar *message = NULL; + gint message_length = -1; + xmlnode *message_body_node = NULL; + xmlnode *message_html_node = NULL; + xmlnode *message_html_body_node = NULL; + xmlnode *message_html_body_font_node = NULL; + xmlnode *message_x_node = NULL; + GaimBuddy *gb = NULL; + BonjourBuddy *bb = NULL; + char *conv_message = NULL; + GaimConversation *conversation = NULL; + char *message_from_ui = NULL; + char *stripped_message = NULL; + + gb = gaim_find_buddy(data->account, to); + if (gb == NULL) + /* You can not send a message to an offline buddy */ + return -10000; + + bb = (BonjourBuddy *)gb->proto_data; + + /* Enclose the message from the UI within a "font" node */ + message_body_node = xmlnode_new("body"); + stripped_message = gaim_markup_strip_html(body); + xmlnode_insert_data(message_body_node, stripped_message, strlen(stripped_message)); + + message_from_ui = g_strconcat("", body, "", NULL); + message_html_body_font_node = xmlnode_from_str(message_from_ui, strlen(message_from_ui)); + + message_html_body_node = xmlnode_new("body"); + xmlnode_insert_child(message_html_body_node, message_html_body_font_node); + + message_html_node = xmlnode_new("html"); + xmlnode_set_attrib(message_html_node, "xmlns", "http://www.w3.org/1999/xhtml"); + xmlnode_insert_child(message_html_node, message_html_body_node); + + message_x_node = xmlnode_new("x"); + xmlnode_set_attrib(message_x_node, "xmlns", "jabber:x:event"); + xmlnode_insert_child(message_x_node, xmlnode_new("composing")); + + message_node = xmlnode_new("message"); + xmlnode_set_attrib(message_node, "to", ((BonjourBuddy*)(gb->proto_data))->name); + xmlnode_set_attrib(message_node, "from", data->account->username); + xmlnode_set_attrib(message_node, "type", "chat"); + xmlnode_insert_child(message_node, message_body_node); + xmlnode_insert_child(message_node, message_html_node); + xmlnode_insert_child(message_node, message_x_node); + + message = xmlnode_to_str(message_node, &message_length); + + /* Check if there is a previously open conversation */ + if (bb->conversation == NULL) + { + bb->conversation = g_new(BonjourJabberConversation, 1); + bb->conversation->socket = _connect_to_buddy(gb); + bb->conversation->stream_started = FALSE; + bb->conversation->buddy_name = g_strdup(gb->name); + bb->conversation->watcher_id = gaim_input_add(bb->conversation->socket, + GAIM_INPUT_READ, _client_socket_handler, gb); + } + + /* Check if the stream for the conversation has been started */ + if (bb->conversation->stream_started == FALSE) + { + /* Start the stream */ + if (send(bb->conversation->socket, DOCTYPE, strlen(DOCTYPE), 0) == -1) + { + gaim_debug_error("bonjour", "Unable to start a conversation\n"); + gaim_debug_warning("bonjour", "send error: %s\n", strerror(errno)); + conv_message = g_strdup(_("Unable to send the message, the conversation couldn't be started.")); + conversation = gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM, bb->name, data->account); + gaim_conversation_write(conversation, NULL, conv_message, GAIM_MESSAGE_SYSTEM, time(NULL)); + close(bb->conversation->socket); + gaim_input_remove(bb->conversation->watcher_id); + + /* Free all the data related to the conversation */ + g_free(bb->conversation->buddy_name); + g_free(bb->conversation); + bb->conversation = NULL; + return 0; + } + + bb->conversation->stream_started = TRUE; + } + + /* Send the message */ + if (_send_data(bb->conversation->socket, message) == -1) + return -10000; + + return 1; +} + +void +bonjour_jabber_close_conversation(BonjourJabber *data, GaimBuddy *gb) +{ + BonjourBuddy *bb = (BonjourBuddy*)gb->proto_data; + + if (bb->conversation != NULL) + { + /* Send the end of the stream to the other end of the conversation */ + send(bb->conversation->socket, STREAM_END, strlen(STREAM_END), 0); + + /* Close the socket and remove the watcher */ + close(bb->conversation->socket); + gaim_input_remove(bb->conversation->watcher_id); + + /* Free all the data related to the conversation */ + g_free(bb->conversation->buddy_name); + g_free(bb->conversation); + bb->conversation = NULL; + } +} + +void +bonjour_jabber_stop(BonjourJabber *data) +{ + GaimBuddy *gb = NULL; + BonjourBuddy *bb = NULL; + GSList *buddies; + GSList *l; + + /* Close the server socket and remove all the watcher */ + close(data->socket); + gaim_input_remove(data->watcher_id); + + /* Close all the sockets and remove all the watchers after sending end streams */ + if (data->account->gc != NULL) + { + buddies = gaim_find_buddies(data->account, data->account->username); + for (l = buddies; l; l = l->next) + { + gb = (GaimBuddy*)l->data; + bb = (BonjourBuddy*)gb->proto_data; + if (bb->conversation != NULL) + { + send(bb->conversation->socket, STREAM_END, strlen(STREAM_END), 0); + close(bb->conversation->socket); + gaim_input_remove(bb->conversation->watcher_id); + } + } + g_slist_free(buddies); + } +} diff -r d10dda2777a9 -r b63ebf84c42b core/protocols/bonjour/jabber.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/protocols/bonjour/jabber.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,65 @@ +/** + * @file jabber.h The Gaim interface to mDNS and peer to peer Jabber. + * + * 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 + * + */ + +#ifndef _BONJOUR_JABBER_H_ +#define _BONJOUR_JABBER_H_ + +#include "account.h" + +#define STREAM_END "" +#define DOCTYPE "\n" + +typedef struct _BonjourJabber +{ + gint port; + gint socket; + gint watcher_id; + GaimAccount* account; +} BonjourJabber; + +typedef struct _BonjourJabberConversation +{ + gint socket; + gint watcher_id; + gchar* buddy_name; + gboolean stream_started; + gint message_id; +} BonjourJabberConversation; + +/** + * Start listening for jabber connections. + * + * @return -1 if there was a problem, else returns the listening + * port number. + */ +gint bonjour_jabber_start(BonjourJabber *data); + +int bonjour_jabber_send_message(BonjourJabber *data, const gchar *to, const gchar *body); + +void bonjour_jabber_close_conversation(BonjourJabber *data, GaimBuddy *gb); + +void bonjour_jabber_stop(BonjourJabber *data); + +#endif /* _BONJOUR_JABBER_H_ */ diff -r d10dda2777a9 -r b63ebf84c42b core/protocols/bonjour/messages.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/protocols/bonjour/messages.txt Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,4 @@ + + +holahola + diff -r d10dda2777a9 -r b63ebf84c42b core/protocols/gg/.todo --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/protocols/gg/.todo Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,8 @@ + + + New User Registration + + + There's probably 100 other things but since I've hardly used the Windows client before I'm not sure what any of them are + + diff -r d10dda2777a9 -r b63ebf84c42b core/protocols/gg/Makefile.am --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/protocols/gg/Makefile.am Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,59 @@ +EXTRA_DIST = \ + Makefile.mingw \ + lib/common.c \ + lib/compat.h \ + lib/COPYING \ + lib/dcc.c \ + lib/events.c \ + lib/http.c \ + lib/libgadu.c \ + lib/libgadu-config.h \ + lib/libgadu.h \ + lib/obsolete.c \ + lib/pubdir50.c \ + lib/pubdir.c + +pkgdir = $(libdir)/gaim + +GGSOURCES = \ + gg-utils.h \ + gg-utils.c \ + confer.h \ + confer.c \ + search.h \ + search.c \ + buddylist.h \ + buddylist.c \ + gg.h \ + gg.c + +AM_CFLAGS = $(st) + +libgg_la_LDFLAGS = -module -avoid-version $(GLIB_LIBS) + +if STATIC_GG + +st = -DGAIM_STATIC_PRPL $(GADU_CFLAGS) +noinst_LIBRARIES = libgg.a +pkg_LTLIBRARIES = + +libgg_a_SOURCES = $(GGSOURCES) +libgg_a_CFLAGS = $(AM_CFLAGS) +libgg_a_LIBADD = $(GADU_LIBS) + +else + +st = $(GADU_CFLAGS) +pkg_LTLIBRARIES = libgg.la +noinst_LIBRARIES = + +libgg_la_SOURCES = $(GGSOURCES) +libgg_la_LIBADD = $(GADU_LIBS) + +endif + +AM_CPPFLAGS = \ + -I$(top_srcdir)/core \ + $(GLIB_CFLAGS) \ + $(DEBUG_CFLAGS) + diff -r d10dda2777a9 -r b63ebf84c42b core/protocols/gg/Makefile.mingw --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/protocols/gg/Makefile.mingw Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,145 @@ +# +# Makefile.mingw +# +# Description: Makefile for win32 (mingw) version of libgg +# + +# +# PATHS +# + +INCLUDE_DIR := . +GTK_TOP := ../../../../win32-dev/gtk_2_0 +GAIM_TOP := ../../.. +GG_ROOT := . +GAIM_INSTALL_DIR := $(GAIM_TOP)/win32-install-dir + +## +## VARIABLE DEFINITIONS +## + +TARGET = libgg + +# Compiler Options + +CFLAGS = -include win32dep.h + +DEFINES = + +# Static or Plugin... +ifeq ($(TYPE),STATIC) + DEFINES += -DSTATIC + DLL_INSTALL_DIR = $(GAIM_INSTALL_DIR) +else +ifeq ($(TYPE),PLUGIN) + DLL_INSTALL_DIR = $(GAIM_INSTALL_DIR)/plugins +endif +endif + + +## +## INCLUDE MAKEFILES +## + +include $(GAIM_TOP)/src/win32/global.mak + +## +## INCLUDE PATHS +## + +INCLUDE_PATHS += -I$(GG_ROOT) \ + -I$(GG_ROOT)/lib \ + -I$(GTK_TOP)/include \ + -I$(GTK_TOP)/include/gtk-2.0 \ + -I$(GTK_TOP)/include/glib-2.0 \ + -I$(GTK_TOP)/include/pango-1.0 \ + -I$(GTK_TOP)/include/atk-1.0 \ + -I$(GTK_TOP)/lib/glib-2.0/include \ + -I$(GTK_TOP)/lib/gtk-2.0/include \ + -I$(GAIM_TOP)/src \ + -I$(GAIM_TOP)/src/win32 \ + -I$(GAIM_TOP) + + +LIB_PATHS = -L$(GTK_TOP)/lib \ + -L$(GAIM_TOP)/src \ + + +## +## SOURCES, OBJECTS +## + +C_SRC = \ + lib/common.c \ + lib/events.c \ + lib/http.c \ + lib/libgadu.c \ + lib/obsolete.c \ + lib/pubdir.c \ + lib/pubdir50.c \ + buddylist.c \ + confer.c \ + gg.c \ + search.c \ + gg-utils.c + +OBJECTS = $(C_SRC:%.c=%.o) + + +## +## LIBRARIES +## + +LIBS = \ + -lglib-2.0 \ + -lgmodule-2.0 \ + -lgobject-2.0 \ + -lintl \ + -lgaim \ + -lws2_32 + + +## +## RULES +## + +# How to make a C file + +%.o: %.c + $(CC) $(CFLAGS) $(DEFINES) $(INCLUDE_PATHS) -o $@ -c $< + +## +## TARGET DEFINITIONS +## + +.PHONY: all clean + +all: $(TARGET).dll + +install: + cp $(GG_ROOT)/$(TARGET).dll $(DLL_INSTALL_DIR) + + +## +## BUILD Dependencies +## + +$(GAIM_TOP)/src/gaim.lib: + $(MAKE) -C $(GAIM_TOP)/src -f Makefile.mingw gaim.lib + +## +## BUILD DLL +## + +$(TARGET).dll: $(OBJECTS) $(GAIM_TOP)/src/gaim.lib + $(CC) -shared $(OBJECTS) $(LIB_PATHS) $(LIBS) $(DLL_LD_FLAGS) -Wl,--out-implib,$(TARGET).lib -o $(TARGET).dll + +## +## CLEAN RULES +## + +clean: + rm -rf *.o + rm -rf $(GG_ROOT)/lib/*.o + rm -rf $(TARGET).dll + rm -rf $(TARGET).lib diff -r d10dda2777a9 -r b63ebf84c42b core/protocols/gg/buddylist.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/protocols/gg/buddylist.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,254 @@ +/** + * @file buddylist.c + * + * gaim + * + * Copyright (C) 2005 Bartosz Oler + * + * 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 + +#include "gg.h" +#include "gg-utils.h" +#include "buddylist.h" + + +/* void ggp_buddylist_send(GaimConnection *gc) {{{ */ +void ggp_buddylist_send(GaimConnection *gc) +{ + GGPInfo *info = gc->proto_data; + GaimAccount *account = gaim_connection_get_account(gc); + + GaimBuddyList *blist; + GaimBlistNode *gnode, *cnode, *bnode; + GaimBuddy *buddy; + uin_t *userlist = NULL; + gchar *types = NULL; + int size = 0; + + if ((blist = gaim_get_blist()) == NULL) + return; + + for (gnode = blist->root; gnode != NULL; gnode = gnode->next) { + if (!GAIM_BLIST_NODE_IS_GROUP(gnode)) + continue; + + for (cnode = gnode->child; cnode != NULL; cnode = cnode->next) { + if (!GAIM_BLIST_NODE_IS_CONTACT(cnode)) + continue; + + for (bnode = cnode->child; bnode != NULL; bnode = bnode->next) { + if (!GAIM_BLIST_NODE_IS_BUDDY(bnode)) + continue; + + buddy = (GaimBuddy *)bnode; + + if (buddy->account != account) + continue; + + size++; + userlist = (uin_t *) g_renew(uin_t, userlist, size); + types = (gchar *) g_renew(gchar, types, size); + userlist[size - 1] = ggp_str_to_uin(buddy->name); + types[size - 1] = GG_USER_NORMAL; + gaim_debug_info("gg", "ggp_buddylist_send: adding %d\n", + userlist[size - 1]); + } + } + } + + if (userlist) { + int ret = gg_notify_ex(info->session, userlist, types, size); + g_free(userlist); + g_free(types); + + gaim_debug_info("gg", "send: ret=%d; size=%d\n", ret, size); + } +} +/* }}} */ + +/* void ggp_buddylist_load(GaimConnection *gc, char *buddylist) {{{ */ +void ggp_buddylist_load(GaimConnection *gc, char *buddylist) +{ + GaimBuddy *buddy; + GaimGroup *group; + gchar **users_tbl; + int i; + + /* + * XXX: Limit of entries in a buddylist that will be imported. + */ + users_tbl = g_strsplit(buddylist, "\r\n", 200); + + for (i = 0; users_tbl[i] != NULL; i++) { + gchar **data_tbl; + gchar *name, *show, *g; + + if (strlen(users_tbl[i]) == 0) + continue; + + data_tbl = g_strsplit(users_tbl[i], ";", 8); + + show = charset_convert(data_tbl[3], "CP1250", "UTF-8"); + name = data_tbl[6]; + + gaim_debug_info("gg", "got buddy: name=%s show=%s\n", name, show); + + if (gaim_find_buddy(gaim_connection_get_account(gc), name)) { + g_free(show); + g_strfreev(data_tbl); + continue; + } + + g = g_strdup("Gadu-Gadu"); + + if (strlen(data_tbl[5])) { + /* Hard limit to at most 50 groups */ + gchar **group_tbl = g_strsplit(data_tbl[5], ",", 50); + if (strlen(group_tbl[0]) > 0) { + g_free(g); + g = g_strdup(group_tbl[0]); + } + g_strfreev(group_tbl); + } + + buddy = gaim_buddy_new(gaim_connection_get_account(gc), name, + strlen(show) ? show : NULL); + + if (!(group = gaim_find_group(g))) { + group = gaim_group_new(g); + gaim_blist_add_group(group, NULL); + } + + gaim_blist_add_buddy(buddy, NULL, group, NULL); + g_free(g); + + g_free(show); + g_strfreev(data_tbl); + } + g_strfreev(users_tbl); + + ggp_buddylist_send(gc); + +} +/* }}} */ + +/* void ggp_buddylist_offline(GaimConnection *gc) {{{ */ +void ggp_buddylist_offline(GaimConnection *gc) +{ + GaimAccount *account = gaim_connection_get_account(gc); + GaimBuddyList *blist; + GaimBlistNode *gnode, *cnode, *bnode; + GaimBuddy *buddy; + + if ((blist = gaim_get_blist()) == NULL) + return; + + for (gnode = blist->root; gnode != NULL; gnode = gnode->next) { + if (!GAIM_BLIST_NODE_IS_GROUP(gnode)) + continue; + + for (cnode = gnode->child; cnode != NULL; cnode = cnode->next) { + if (!GAIM_BLIST_NODE_IS_CONTACT(cnode)) + continue; + + for (bnode = cnode->child; bnode != NULL; bnode = bnode->next) { + if (!GAIM_BLIST_NODE_IS_BUDDY(bnode)) + continue; + + buddy = (GaimBuddy *)bnode; + + if (buddy->account != account) + continue; + + gaim_prpl_got_user_status( + gaim_connection_get_account(gc), + buddy->name, "offline", NULL); + + gaim_debug_info("gg", + "ggp_buddylist_offline: gone: %s\n", + buddy->name); + } + } + } +} +/* }}} */ + +/* char *ggp_buddylist_dump(GaimAccount *account) {{{ */ +char *ggp_buddylist_dump(GaimAccount *account) +{ + GaimBuddyList *blist; + GaimBlistNode *gnode, *cnode, *bnode; + GaimGroup *group; + GaimBuddy *buddy; + + char *buddylist = g_strdup(""); + char *ptr; + + if ((blist = gaim_get_blist()) == NULL) + return NULL; + + for (gnode = blist->root; gnode != NULL; gnode = gnode->next) { + if (!GAIM_BLIST_NODE_IS_GROUP(gnode)) + continue; + + group = (GaimGroup *)gnode; + + for (cnode = gnode->child; cnode != NULL; cnode = cnode->next) { + if (!GAIM_BLIST_NODE_IS_CONTACT(cnode)) + continue; + + for (bnode = cnode->child; bnode != NULL; bnode = bnode->next) { + gchar *newdata, *name, *alias, *gname; + gchar *cp_alias, *cp_gname; + + if (!GAIM_BLIST_NODE_IS_BUDDY(bnode)) + continue; + + buddy = (GaimBuddy *)bnode; + if (buddy->account != account) + continue; + + name = buddy->name; + alias = buddy->alias ? buddy->alias : buddy->name; + gname = group->name; + + cp_gname = charset_convert(gname, "UTF-8", "CP1250"); + cp_alias = charset_convert(alias, "UTF-8", "CP1250"); + newdata = g_strdup_printf( + "%s;%s;%s;%s;%s;%s;%s;%s%s\r\n", + cp_alias, cp_alias, cp_alias, cp_alias, + "", cp_gname, name, "", ""); + + ptr = buddylist; + buddylist = g_strconcat(ptr, newdata, NULL); + + g_free(newdata); + g_free(ptr); + g_free(cp_gname); + g_free(cp_alias); + } + } + } + + return buddylist; +} +/* }}} */ + + +/* vim: set ts=8 sts=0 sw=8 noet: */ diff -r d10dda2777a9 -r b63ebf84c42b core/protocols/gg/buddylist.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/protocols/gg/buddylist.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,65 @@ +/** + * @file buddylist.h + * + * gaim + * + * Copyright (C) 2005 Bartosz Oler + * + * 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_GG_BUDDYLIST_H +#define _GAIM_GG_BUDDYLIST_H + +#include "connection.h" +#include "account.h" + +void +ggp_buddylist_send(GaimConnection *gc); + +/** + * Load buddylist from server into the rooster. + * + * @param gc GaimConnection + * @param buddylist Pointer to the buddylist that will be loaded. + */ +/* void ggp_buddylist_load(GaimConnection *gc, char *buddylist) {{{ */ +void +ggp_buddylist_load(GaimConnection *gc, char *buddylist); + +/** + * Set offline status for all buddies. + * + * @param gc Connection handler + */ +void +ggp_buddylist_offline(GaimConnection *gc); + +/** + * Get all the buddies in the current account. + * + * @param account Current account. + * + * @return List of buddies. + */ +char * +ggp_buddylist_dump(GaimAccount *account); + + +#endif /* _GAIM_GG_BUDDYLIST_H */ + + +/* vim: set ts=8 sts=0 sw=8 noet: */ diff -r d10dda2777a9 -r b63ebf84c42b core/protocols/gg/confer.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/protocols/gg/confer.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,170 @@ +/** + * @file confer.c + * + * gaim + * + * Copyright (C) 2005 Bartosz Oler + * + * 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 +#include "gg.h" +#include "gg-utils.h" +#include "confer.h" + +/* GaimConversation *ggp_confer_find_by_name(GaimConnection *gc, const gchar *name) {{{ */ +GaimConversation *ggp_confer_find_by_name(GaimConnection *gc, const gchar *name) +{ + g_return_val_if_fail(gc != NULL, NULL); + g_return_val_if_fail(name != NULL, NULL); + + return gaim_find_conversation_with_account(GAIM_CONV_TYPE_CHAT, name, + gaim_connection_get_account(gc)); +} +/* }}} */ + +/* void ggp_confer_participants_add_uin(GaimConnection *gc, const gchar *chat_name, const uin_t uin) {{{ */ +void ggp_confer_participants_add_uin(GaimConnection *gc, const gchar *chat_name, + const uin_t uin) +{ + GaimConversation *conv; + GGPInfo *info = gc->proto_data; + GGPChat *chat; + GList *l; + gchar *str_uin; + + for (l = info->chats; l != NULL; l = l->next) { + chat = l->data; + + if (g_utf8_collate(chat->name, chat_name) != 0) + continue; + + if (g_list_find(chat->participants, GINT_TO_POINTER(uin)) == NULL) { + chat->participants = g_list_append( + chat->participants, GINT_TO_POINTER(uin)); + + str_uin = g_strdup_printf("%lu", (unsigned long int)uin); + conv = ggp_confer_find_by_name(gc, chat_name); + gaim_conv_chat_add_user(GAIM_CONV_CHAT(conv), str_uin, NULL, + GAIM_CBFLAGS_NONE, TRUE); + + g_free(str_uin); + } + break; + } +} +/* }}} */ + +/* void ggp_confer_participants_add(GaimConnection *gc, const gchar *chat_name, const uin_t *recipients, int count) {{{ */ +void ggp_confer_participants_add(GaimConnection *gc, const gchar *chat_name, + const uin_t *recipients, int count) +{ + GGPInfo *info = gc->proto_data; + GList *l; + gchar *str_uin; + + for (l = info->chats; l != NULL; l = l->next) { + GGPChat *chat = l->data; + int i; + + if (g_utf8_collate(chat->name, chat_name) != 0) + continue; + + for (i = 0; i < count; i++) { + GaimConversation *conv; + + if (g_list_find(chat->participants, + GINT_TO_POINTER(recipients[i])) != NULL) { + continue; + } + + chat->participants = g_list_append(chat->participants, + GINT_TO_POINTER(recipients[i])); + + str_uin = g_strdup_printf("%lu", (unsigned long int)recipients[i]); + conv = ggp_confer_find_by_name(gc, chat_name); + gaim_conv_chat_add_user(GAIM_CONV_CHAT(conv), + str_uin, NULL, + GAIM_CBFLAGS_NONE, TRUE); + g_free(str_uin); + } + break; + } +} +/* }}} */ + +/* const char *ggp_confer_find_by_participants(GaimConnection *gc, const uin_t *recipients, int count) {{{ */ +const char *ggp_confer_find_by_participants(GaimConnection *gc, + const uin_t *recipients, int count) +{ + GGPInfo *info = gc->proto_data; + GGPChat *chat = NULL; + GList *l; + int matches; + + g_return_val_if_fail(info->chats != NULL, NULL); + + for (l = info->chats; l != NULL; l = l->next) { + GList *m; + + chat = l->data; + matches = 0; + + for (m = chat->participants; m != NULL; m = m->next) { + uin_t uin = GPOINTER_TO_INT(m->data); + int i; + + for (i = 0; i < count; i++) + if (uin == recipients[i]) + matches++; + } + + if (matches == count) + break; + + chat = NULL; + } + + if (chat == NULL) + return NULL; + else + return chat->name; +} +/* }}} */ + +/* const char *ggp_confer_add_new(GaimConnection *gc, const char *name) {{{ */ +const char *ggp_confer_add_new(GaimConnection *gc, const char *name) +{ + GGPInfo *info = gc->proto_data; + GGPChat *chat; + + chat = g_new0(GGPChat, 1); + + if (name == NULL) + chat->name = g_strdup_printf("conf#%d", info->chats_count++); + else + chat->name = g_strdup(name); + + chat->participants = NULL; + + info->chats = g_list_append(info->chats, chat); + + return chat->name; +} +/* }}} */ + +/* vim: set ts=8 sts=0 sw=8 noet: */ diff -r d10dda2777a9 -r b63ebf84c42b core/protocols/gg/confer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/protocols/gg/confer.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,93 @@ +/** + * @file confer.h + * + * gaim + * + * Copyright (C) 2005 Bartosz Oler + * + * 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_GG_CONFER_H +#define _GAIM_GG_CONFER_H + +#include "gg.h" + +/** + * Finds a CHAT conversation for the current account with the specified name. + * + * @param gc GaimConnection instance. + * @param name Name of the conversation. + * + * @return GaimConversation or NULL if not found. + */ +GaimConversation * +ggp_confer_find_by_name(GaimConnection *gc, const gchar *name); + +/** + * Adds the specified UIN to the specified conversation. + * + * @param gc GaimConnection. + * @param chat_name Name of the conversation. + */ +void +ggp_confer_participants_add_uin(GaimConnection *gc, const gchar *chat_name, + const uin_t uin); + +/** + * Add the specified UINs to the specified conversation. + * + * @param gc GaimConnection. + * @param chat_name Name of the conversation. + * @param recipients List of the UINs. + * @param count Number of the UINs. + */ +void +ggp_confer_participants_add(GaimConnection *gc, const gchar *chat_name, + const uin_t *recipients, int count); + +/** + * Finds a conversation in which all the specified recipients participate. + * + * TODO: This function should be rewritten to better handle situations when + * somebody adds more people to the converation. + * + * @param gc GaimConnection. + * @param recipients List of the people in the conversation. + * @param count Number of people. + * + * @return Name of the conversation. + */ +const char* +ggp_confer_find_by_participants(GaimConnection *gc, const uin_t *recipients, + int count); + +/** + * Adds a new conversation to the internal list of conversations. + * If name is NULL then it will be automagically generated. + * + * @param gc GaimConnection. + * @param name Name of the conversation. + * + * @return Name of the conversation. + */ +const char* +ggp_confer_add_new(GaimConnection *gc, const char *name); + + +#endif /* _GAIM_GG_CONFER_H */ + +/* vim: set ts=8 sts=0 sw=8 noet: */ diff -r d10dda2777a9 -r b63ebf84c42b core/protocols/gg/gg-utils.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/protocols/gg/gg-utils.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,104 @@ +/** + * @file gg-utils.c + * + * gaim + * + * Copyright (C) 2005 Bartosz Oler + * + * 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 "gg-utils.h" + + +/* uin_t ggp_str_to_uin(const char *str) {{{ */ +uin_t ggp_str_to_uin(const char *str) +{ + char *tmp; + long num; + + if (!str) + return 0; + + errno = 0; + num = strtol(str, &tmp, 10); + + if (*str == '\0' || *tmp != '\0') + return 0; + + if ((errno == ERANGE || (num == LONG_MAX || num == LONG_MIN)) +#if (LONG_MAX > UINT_MAX) + || num > (long)UINT_MAX +#endif + || num < 0) + return 0; + + return (uin_t) num; +} +/* }}} */ + +/* char *charset_convert(const gchar *locstr, const char *encsrc, const char *encdst) {{{ */ +char *charset_convert(const gchar *locstr, const char *encsrc, const char *encdst) +{ + gchar *msg; + GError *err = NULL; + + if (locstr == NULL) + return NULL; + + msg = g_convert_with_fallback(locstr, strlen(locstr), encdst, encsrc, + "?", NULL, NULL, &err); + if (err != NULL) { + gaim_debug_error("gg", "Error converting from %s to %s: %s\n", + encsrc, encdst, err->message); + g_error_free(err); + } + + /* Just in case? */ + if (msg == NULL) + msg = g_strdup(locstr); + + return msg; +} +/* }}} */ + +/* ggp_get_uin(GaimAccount *account) {{{ */ +uin_t ggp_get_uin(GaimAccount *account) +{ + return ggp_str_to_uin(gaim_account_get_username(account)); +} +/* }}} */ + +/* char *ggp_buddy_get_name(GaimConnection *gc, const uin_t uin) {{{ */ +char *ggp_buddy_get_name(GaimConnection *gc, const uin_t uin) +{ + GaimBuddy *buddy; + gchar *str_uin; + + str_uin = g_strdup_printf("%lu", (unsigned long int)uin); + + buddy = gaim_find_buddy(gaim_connection_get_account(gc), str_uin); + if (buddy != NULL) { + g_free(str_uin); + return g_strdup(gaim_buddy_get_alias(buddy)); + } else { + return str_uin; + } +} +/* }}} */ + + +/* vim: set ts=8 sts=0 sw=8 noet: */ diff -r d10dda2777a9 -r b63ebf84c42b core/protocols/gg/gg-utils.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/protocols/gg/gg-utils.h Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,86 @@ +/** + * @file gg-utils.h + * + * gaim + * + * Copyright (C) 2005 Bartosz Oler + * + * 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_GG_UTILS_H +#define _GAIM_GG_UTILS_H + +#include "internal.h" + +#include "plugin.h" +#include "version.h" +#include "notify.h" +#include "status.h" +#include "blist.h" +#include "accountopt.h" +#include "debug.h" +#include "util.h" +#include "request.h" + +#include "gg.h" + + +/* + * Convert a base 10 string to a UIN. + * + * @param str The string to convert + * @return UIN or 0 if an error occurred. + */ +uin_t +ggp_str_to_uin(const char *str); + +/** + * Convert enconding of a given string. + * + * @param locstr Input string. + * @param encsrc Current encoding of the string. + * @param encdst Target encoding of the string. + * + * @return Converted string (it must be g_free()ed when not used. Or NULL if + * locstr is NULL. + */ +char * +charset_convert(const gchar *locstr, const char *encsrc, const char *encdst); + +/** + * Get UIN of a given account. + * + * @param account Current account. + * + * @return UIN of an account. + */ +uin_t +ggp_get_uin(GaimAccount *account); + +/** + * Returns the best name of a buddy from the buddylist. + * + * @param gc GaimConnection instance. + * @param uin UIN of the buddy. + * + * @return Name of the buddy, or UIN converted to string. + */ +char * +ggp_buddy_get_name(GaimConnection *gc, const uin_t uin); + +#endif /* _GAIM_GG_UTILS_H */ + +/* vim: set ts=8 sts=0 sw=8 noet: */ diff -r d10dda2777a9 -r b63ebf84c42b core/protocols/gg/gg.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/protocols/gg/gg.c Sat Dec 16 04:59:55 2006 +0000 @@ -0,0 +1,2192 @@ +/** + * @file gg.c Gadu-Gadu protocol plugin + * + * gaim + * + * Copyright (C) 2005 Bartosz Oler + * + * Some parts of the code are adapted or taken from the previous implementation + * of this plugin written by Arkadiusz Miskiewicz + * + * Thanks to Google's Summer of Code Program. + * + * 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 "version.h" +#include "notify.h" +#include "status.h" +#include "blist.h" +#include "accountopt.h" +#include "debug.h" +#include "util.h" +#include "request.h" + +#include + +#include "gg.h" +#include "confer.h" +#include "search.h" +#include "buddylist.h" +#include "gg-utils.h" + +static GaimPlugin *my_protocol = NULL; + +/* ---------------------------------------------------------------------- */ +/* ----- EXTERNAL CALLBACKS --------------------------------------------- */ +/* ---------------------------------------------------------------------- */ + + +/* ----- HELPERS -------------------------------------------------------- */ + +/** + * Set up libgadu's proxy. + * + * @param account Account for which to set up the proxy. + * + * @return Zero if proxy setup is valid, otherwise -1. + */ +/* static int ggp_setup_proxy(GaimAccount *account) {{{ */ +static int ggp_setup_proxy(GaimAccount *account) +{ + GaimProxyInfo *gpi; + + gpi = gaim_proxy_get_setup(account); + + if ((gaim_proxy_info_get_type(gpi) != GAIM_PROXY_NONE) && + (gaim_proxy_info_get_host(gpi) == NULL || + gaim_proxy_info_get_port(gpi) <= 0)) { + + gg_proxy_enabled = 0; + gaim_notify_error(NULL, NULL, _("Invalid proxy settings"), + _("Either the host name or port number specified for your given proxy type is invalid.")); + return -1; + } else if (gaim_proxy_info_get_type(gpi) != GAIM_PROXY_NONE) { + gg_proxy_enabled = 1; + gg_proxy_host = g_strdup(gaim_proxy_info_get_host(gpi)); + gg_proxy_port = gaim_proxy_info_get_port(gpi); + gg_proxy_username = g_strdup(gaim_proxy_info_get_username(gpi)); + gg_proxy_password = g_strdup(gaim_proxy_info_get_password(gpi)); + } else { + gg_proxy_enabled = 0; + } + + return 0; +} +/* }}} */ + +/* + */ +/* static void ggp_async_token_handler(gpointer _gc, gint fd, GaimInputCondition cond) {{{ */ +static void ggp_async_token_handler(gpointer _gc, gint fd, GaimInputCondition cond) +{ + GaimConnection *gc = _gc; + GGPInfo *info = gc->proto_data; + GGPToken *token = info->token; + GGPTokenCallback cb; + + struct gg_token *t = NULL; + + gaim_debug_info("gg", "token_handler: token->req: check = %d; state = %d;\n", + token->req->check, token->req->state); + + if (gg_token_watch_fd(token->req) == -1 || token->req->state == GG_STATE_ERROR) { + gaim_debug_error("gg", "token error (1): %d\n", token->req->error); + gaim_input_remove(token->inpa); + gg_token_free(token->req); + token->req = NULL; + + gaim_notify_error(gaim_connection_get_account(gc), + _("Token Error"), + _("Unable to fetch the token.\n"), NULL); + return; + } + + if (token->req->state != GG_STATE_DONE) { + gaim_input_remove(token->inpa); + token->inpa = gaim_input_add(token->req->fd, + (token->req->check == 1) + ? GAIM_INPUT_WRITE + : GAIM_INPUT_READ, + ggp_async_token_handler, gc); + return; + } + + if (!(t = token->req->data) || !token->req->body) { + gaim_debug_error("gg", "token error (2): %d\n", token->req->error); + gaim_input_remove(token->inpa); + gg_token_free(token->req); + token->req = NULL; + + gaim_notify_error(gaim_connection_get_account(gc), + _("Token Error"), + _("Unable to fetch the token.\n"), NULL); + return; + } + + gaim_input_remove(token->inpa); + + token->id = g_strdup(t->tokenid); + token->size = token->req->body_size; + token->data = g_new0(char, token->size); + memcpy(token->data, token->req->body, token->size); + + gaim_debug_info("gg", "TOKEN! tokenid = %s; size = %d\n", + token->id, token->size); + + gg_token_free(token->req); + token->req = NULL; + token->inpa = 0; + + cb = token->cb; + token->cb = NULL; + cb(gc); +} +/* }}} */ + +/* + */ +/* static void ggp_token_request(GaimConnection *gc, GGPTokenCallback cb) {{{ */ +static void ggp_token_request(GaimConnection *gc, GGPTokenCallback cb) +{ + GaimAccount *account; + struct gg_http *req; + GGPInfo *info; + + account = gaim_connection_get_account(gc); + + if (ggp_setup_proxy(account) == -1) + return; + + info = gc->proto_data; + + if ((req = gg_token(1)) == NULL) { + gaim_notify_error(account, + _("Token Error"), + _("Unable to fetch the token.\n"), NULL); + return; + } + + info->token = g_new(GGPToken, 1); + info->token->cb = cb; + + info->token->req = req; + info->token->inpa = gaim_input_add(req->fd, GAIM_INPUT_READ, + ggp_async_token_handler, gc); +} +/* }}} */ + +/* ---------------------------------------------------------------------- */ + +/** + * Request buddylist from the server. + * Buddylist is received in the ggp_callback_recv(). + * + * @param Current action handler. + */ +/* static void ggp_action_buddylist_get(GaimPluginAction *action) {{{ */ +static void ggp_action_buddylist_get(GaimPluginAction *action) +{ + GaimConnection *gc = (GaimConnection *)action->context; + GGPInfo *info = gc->proto_data; + + gaim_debug_info("gg", "Downloading...\n"); + + gg_userlist_request(info->session, GG_USERLIST_GET, NULL); +} +/* }}} */ + +/** + * Upload the buddylist to the server. + * + * @param action Current action handler. + */ +/* static void ggp_action_buddylist_put(GaimPluginAction *action) {{{ */ +static void ggp_action_buddylist_put(GaimPluginAction *action) +{ + GaimConnection *gc = (GaimConnection *)action->context; + GGPInfo *info = gc->proto_data; + + char *buddylist = ggp_buddylist_dump(gaim_connection_get_account(gc)); + + gaim_debug_info("gg", "Uploading...\n"); + + if (buddylist == NULL) + return; + + gg_userlist_request(info->session, GG_USERLIST_PUT, buddylist); + g_free(buddylist); +} +/* }}} */ + +/** + * Delete buddylist from the server. + * + * @param action Current action handler. + */ +/* static void ggp_action_buddylist_delete(GaimPluginAction *action) {{{ */ +static void ggp_action_buddylist_delete(GaimPluginAction *action) +{ + GaimConnection *gc = (GaimConnection *)action->context; + GGPInfo *info = gc->proto_data; + + gaim_debug_info("gg", "Deleting...\n"); + + gg_userlist_request(info->session, GG_USERLIST_PUT, NULL); +} +/* }}} */ + +/* + */ +/* static void ggp_callback_buddylist_save_ok(GaimConnection *gc, gchar *file) {{{ */ +static void ggp_callback_buddylist_save_ok(GaimConnection *gc, gchar *file) +{ + GaimAccount *account = gaim_connection_get_account(gc); + + FILE *fh; + char *buddylist = ggp_buddylist_dump(account); + gchar *msg; + + gaim_debug_info("gg", "Saving...\n"); + gaim_debug_info("gg", "file = %s\n", file); + + if (buddylist == NULL) { + gaim_notify_info(account, _("Save Buddylist..."), + _("Your buddylist is empty, nothing was written to the file."), + NULL); + return; + } + + if ((fh = g_fopen(file, "wb")) == NULL) { + msg = g_strconcat(_("Couldn't open file"), ": ", file, "\n", NULL); + gaim_debug_error("gg", "Could not open file: %s\n", file); + gaim_notify_error(account, _("Couldn't open file"), msg, NULL); + g_free(msg); + g_free(file); + return; + } + + fwrite(buddylist, sizeof(char), g_utf8_strlen(buddylist, -1), fh); + fclose(fh); + g_free(buddylist); + + gaim_notify_info(account, _("Save Buddylist..."), + _("Buddylist saved successfully!"), NULL); +} +/* }}} */ + +/* + */ +/* static void ggp_callback_buddylist_load_ok(GaimConnection *gc, gchar *file) {{{ */ +static void ggp_callback_buddylist_load_ok(GaimConnection *gc, gchar *file) +{ + GaimAccount *account = gaim_connection_get_account(gc); + GError *error = NULL; + char *buddylist = NULL; + gsize length; + + gaim_debug_info("gg", "file_name = %s\n", file); + + if (!g_file_get_contents(file, &buddylist, &length, &error)) { + gaim_notify_error(account, + _("Couldn't load buddylist"), + _("Couldn't load buddylist"), + error->message); + + gaim_debug_error("gg", + "Couldn't load buddylist. file = %s; error = %s\n", + file, error->message); + + g_error_free(error); + + return; + } + + ggp_buddylist_load(gc, buddylist); + g_free(buddylist); + + gaim_notify_info(account, + _("Load Buddylist..."), + _("Buddylist loaded successfully!"), NULL); +} +/* }}} */ + +/* + */ +/* static void ggp_action_buddylist_save(GaimPluginAction *action) {{{ */ +static void ggp_action_buddylist_save(GaimPluginAction *action) +{ + GaimConnection *gc = (GaimConnection *)action->context; + + gaim_request_file(action, _("Save buddylist..."), NULL, TRUE, + G_CALLBACK(ggp_callback_buddylist_save_ok), NULL, gc); +} +/* }}} */ + +/* + */ +/* static void ggp_action_buddylist_load(GaimPluginAction *action) {{{ */ +static void ggp_action_buddylist_load(GaimPluginAction *action) +{ + GaimConnection *gc = (GaimConnection *)action->context; + + gaim_request_file(action, "Load buddylist from file...", NULL, FALSE, + G_CALLBACK(ggp_callback_buddylist_load_ok), NULL, gc); +} +/* }}} */ + +/* + */ +/* static void ggp_callback_register_account_ok(GaimConnection *gc, GaimRequestFields *fields) {{{ */ +static void ggp_callback_register_account_ok(GaimConnection *gc, + GaimRequestFields *fields) +{ + GaimAccount *account; + GGPInfo *info = gc->proto_data; + struct gg_http *h = NULL; + struct gg_pubdir *s; + uin_t uin; + gchar *email, *p1, *p2, *t; + GGPToken *token = info->token; + + email = charset_convert(gaim_request_fields_get_string(fields, "email"), + "UTF-8", "CP1250"); + p1 = charset_convert(gaim_request_fields_get_string(fields, "password1"), + "UTF-8", "CP1250"); + p2 = charset_convert(gaim_request_fields_get_string(fields, "password2"), + "UTF-8", "CP1250"); + t = charset_convert(gaim_request_fields_get_string(fields, "token"), + "UTF-8", "CP1250"); + + account = gaim_connection_get_account(gc); + + if (email == NULL || p1 == NULL || p2 == NULL || t == NULL || + *email == '\0' || *p1 == '\0' || *p2 == '\0' || *t == '\0') { + gaim_connection_error(gc, _("Fill in the registration fields.")); + goto exit_err; + } + + if (g_utf8_collate(p1, p2) != 0) { + gaim_connection_error(gc, _("Passwords do not match.")); + goto exit_err; + } + + gaim_debug_info("gg", "register_account_ok: token_id = %d; t = %s\n", + token->id, t); + h = gg_register3(email, p1, token->id, t, 0); + if (h == NULL || !(s = h->data) || !s->success) { + gaim_connection_error(gc, + _("Unable to register new account. Error occurred.\n")); + goto exit_err; + } + + uin = s->uin; + gaim_debug_info("gg", "registered uin: %d\n", uin); + + g_free(t); + t = g_strdup_printf("%u", uin); + gaim_account_set_username(account, t); + /* Save the password if remembering passwords for the account */ + gaim_account_set_password(account, p1); + + gaim_notify_info(NULL, _("New Gadu-Gadu Account Registered"), + _("Registration completed successfully!"), NULL); + + /* TODO: the currently open Accounts Window will not be updated withthe + * new username and etc, we need to somehow have it refresh at this + * point + */ + + /* Need to disconnect or actually log in. For now, we disconnect. */ + gaim_connection_destroy(gc); + +exit_err: + gg_register_free(h); + g_free(email); + g_free(p1); + g_free(p2); + g_free(t); + g_free(token->id); + g_free(token); +} +/* }}} */ + +/* + */ +/* static void ggp_callback_register_account_cancel(GaimConnection *gc, GaimRequestFields *fields) {{{ */ +static void ggp_callback_register_account_cancel(GaimConnection *gc, + GaimRequestFields *fields) +{ + GGPInfo *info = gc->proto_data; + GGPToken *token = info->token; + + gaim_connection_destroy(gc); + + g_free(token->id); + g_free(token->data); + g_free(token); + +} +/* }}} */ + +/* + */ +/* static void ggp_register_user_dialog(GaimConnection *gc) {{{ */ +static void ggp_register_user_dialog(GaimConnection *gc) +{ + GaimAccount *account; + GaimRequestFields *fields; + GaimRequestFieldGroup *group; + GaimRequestField *field; + + GGPInfo *info = gc->proto_data; + GGPToken *token = info->token; + + + account = gaim_connection_get_account(gc); + + fields = gaim_request_fields_new(); + group = gaim_request_field_group_new(NULL); + gaim_request_fields_add_group(fields, group); + + field = gaim_request_field_string_new("email", + _("E-mail"), "", FALSE); + gaim_request_field_string_set_masked(field, FALSE); + gaim_request_field_group_add_field(group, field); + + field = gaim_request_field_string_new("password1", + _("Password"), "", FALSE); + gaim_request_field_string_set_masked(field, TRUE); + gaim_request_field_group_add_field(group, field); + + field = gaim_request_field_string_new("password2", + _("Password (retype)"), "", FALSE); + gaim_request_field_string_set_masked(field, TRUE); + gaim_request_field_group_add_field(group, field); + + field = gaim_request_field_string_new("token", + _("Enter current token"), "", FALSE); + gaim_request_field_string_set_masked(field, FALSE); + gaim_request_field_group_add_field(group, field); + + /* original size: 60x24 */ + field = gaim_request_field_image_new("token_img", + _("Current token"), token->data, token->size); + gaim_request_field_group_add_field(group, field); + + gaim_request_fields(account, + _("Register New Gadu-Gadu Account"), + _("Register New Gadu-Gadu Account"), + _("Please, fill in the following fields"), + fields, + _("OK"), G_CALLBACK(ggp_callback_register_account_ok), + _("Cancel"), G_CALLBACK(ggp_callback_register_account_cancel), + gc); +} +/* }}} */ + +/* ----- PUBLIC DIRECTORY SEARCH ---------------------------------------- */ + +/* + */ +/* static void ggp_callback_show_next(GaimConnection *gc, GList *row, gpointer user_data) {{{ */ +static void ggp_callback_show_next(GaimConnection *gc, GList *row, gpointer user_data) +{ + GGPInfo *info = gc->proto_data; + GGPSearchForm *form = user_data; + guint32 seq; + + g_free(form->offset); + form->offset = g_strdup(form->last_uin); + + ggp_search_remove(info->searches, form->seq); + + seq = ggp_search_start(gc, form); + ggp_search_add(info->searches, seq, form); +} +/* }}} */ + +/* + */ +/* static void ggp_callback_add_buddy(GaimConnection *gc, GList *row, gpointer user_data) {{{ */ +static void ggp_callback_add_buddy(GaimConnection *gc, GList *row, gpointer user_data) +{ + gaim_blist_request_add_buddy(gaim_connection_get_account(gc), + g_list_nth_data(row, 0), NULL, NULL); +} +/* }}} */ + +/* + */ +/* static void ggp_callback_im(GaimConnection *gc, GList *row, gpointer user_data) {{{ */ +static void ggp_callback_im(GaimConnection *gc, GList *row, gpointer user_data) +{ + GaimAccount *account; + GaimConversation *conv; + char *name; + + account = gaim_connection_get_account(gc); + + name = g_list_nth_data(row, 0); + conv = gaim_conversation_new(GAIM_CONV_TYPE_IM, account, name); + gaim_conversation_present(conv); +} +/* }}} */ + +/* + */ +/* static void ggp_callback_find_buddies(GaimConnection *gc, GaimRequestFields *fields) {{{ */ +static void ggp_callback_find_buddies(GaimConnection *gc, GaimRequestFields *fields) +{ + GGPInfo *info = gc->proto_data; + GGPSearchForm *form; + guint32 seq; + + form = ggp_search_form_new(GGP_SEARCH_TYPE_FULL); + + form->user_data = info; + form->lastname = charset_convert( + gaim_request_fields_get_string(fields, "lastname"), + "UTF-8", "CP1250"); + form->firstname = charset_convert( + gaim_request_fields_get_string(fields, "firstname"), + "UTF-8", "CP1250"); + form->nickname = charset_convert( + gaim_request_fields_get_string(fields, "nickname"), + "UTF-8", "CP1250"); + form->city = charset_convert( + gaim_request_fields_get_string(fields, "city"), + "UTF-8", "CP1250"); + form->birthyear = charset_convert( + gaim_request_fields_get_string(fields, "year"), + "UTF-8", "CP1250"); + + switch (gaim_request_fields_get_choice(fields, "gender")) { + case 1: + form->gender = g_strdup(GG_PUBDIR50_GENDER_MALE); + break; + case 2: + form->gender = g_strdup(GG_PUBDIR50_GENDER_FEMALE); + break; + default: + form->gender = NULL; + break; + } + + form->active = gaim_request_fields_get_bool(fields, "active") + ? g_strdup(GG_PUBDIR50_ACTIVE_TRUE) : NULL; + + form->offset = g_strdup("0"); + + seq = ggp_search_start(gc, form); + ggp_search_add(info->searches, seq, form); +} +/* }}} */ + +/* + */ +/* static void ggp_find_buddies(GaimPluginAction *action) {{{ */ +static void ggp_find_buddies(GaimPluginAction *action) +{ + GaimConnection *gc = (GaimConnection *)action->context; + + GaimRequestFields *fields; + GaimRequestFieldGroup *group; + GaimRequestField *field; + + fields = gaim_request_fields_new(); + group = gaim_request_field_group_new(NULL); + gaim_request_fields_add_group(fields, group); + + field = gaim_request_field_string_new("lastname", + _("Last name"), NULL, FALSE); + gaim_request_field_string_set_masked(field, FALSE); + gaim_request_field_group_add_field(group, field); + + field = gaim_request_field_string_new("firstname", + _("First name"), NULL, FALSE); + gaim_request_field_string_set_masked(field, FALSE); + gaim_request_field_group_add_field(group, field); + + field = gaim_request_field_string_new("nickname", + _("Nickname"), NULL, FALSE); + gaim_request_field_string_set_masked(field, FALSE); + gaim_request_field_group_add_field(group, field); + + field = gaim_request_field_string_new("city", + _("City"), NULL, FALSE); + gaim_request_field_string_set_masked(field, FALSE); + gaim_request_field_group_add_field(group, field); + + field = gaim_request_field_string_new("year", + _("Year of birth"), NULL, FALSE); + gaim_request_field_group_add_field(group, field); + + field = gaim_request_field_choice_new("gender", _("Gender"), 0); + gaim_request_field_choice_add(field, _("Male or female")); + gaim_request_field_choice_add(field, _("Male")); + gaim_request_field_choice_add(field, _("Female")); + gaim_request_field_group_add_field(group, field); + + field = gaim_request_field_bool_new("active", + _("Only online"), FALSE); + gaim_request_field_group_add_field(group, field); + + gaim_request_fields(gc, + _("Find buddies"), + _("Find buddies"), + _("Please, enter your search criteria below"), + fields, + _("OK"), G_CALLBACK(ggp_callback_find_buddies), + _("Cancel"), NULL, + gc); +} +/* }}} */ + +/* ----- CHANGE PASSWORD ------------------------------------------------ */ + +/* + */ +/* static void ggp_callback_change_passwd_ok(GaimConnection *gc, GaimRequestFields *fields) {{{ */ +static void ggp_callback_change_passwd_ok(GaimConnection *gc, GaimRequestFields *fields) +{ + GaimAccount *account; + GGPInfo *info = gc->proto_data; + struct gg_http *h; + gchar *cur, *p1, *p2, *t; + + cur = charset_convert( + gaim_request_fields_get_string(fields, "password_cur"), + "UTF-8", "CP1250"); + p1 = charset_convert( + gaim_request_fields_get_string(fields, "password1"), + "UTF-8", "CP1250"); + p2 = charset_convert( + gaim_request_fields_get_string(fields, "password2"), + "UTF-8", "CP1250"); + t = charset_convert( + gaim_request_fields_get_string(fields, "token"), + "UTF-8", "CP1250"); + + account = gaim_connection_get_account(gc); + + if (cur == NULL || p1 == NULL || p2 == NULL || t == NULL || + *cur == '\0' || *p1 == '\0' || *p2 == '\0' || *t == '\0') { + gaim_notify_error(account, NULL, _("Fill in the fields."), NULL); + goto exit_err; + } + + if (g_utf8_collate(p1, p2) != 0) { + gaim_notify_error(account, NULL, + _("New passwords do not match."), NULL); + goto exit_err; + } + + if (g_utf8_collate(cur, gaim_account_get_password(account)) != 0) { + gaim_notify_error(account, NULL, + _("Your current password is different from the one that you specified."), + NULL); + goto exit_err; + } + + gaim_debug_info("gg", "Changing password\n"); + + /* XXX: this e-mail should be a pref... */ + h = gg_change_passwd4(ggp_get_uin(account), + "user@example.net", gaim_account_get_password(account), + p1, info->token->id, t, 0); + + if (h == NULL) { + gaim_notify_error(account, NULL, + _("Unable to change password. Error occured.\n"), + NULL); + goto exit_err; + } + + gaim_account_set_password(account, p1); + + gg_change_passwd_free(h); + + gaim_notify_info(account, _("Change password for the Gadu-Gadu account"), + _("Password was changed successfully!"), NULL); + +exit_err: + g_free(cur); + g_free(p1); + g_free(p2); + g_free(t); + g_free(info->token->id); + g_free(info->token->data); + g_free(info->token); +} +/* }}} */ + +/* + */ +/* static void ggp_change_passwd_dialog(GaimConnection *gc) {{{ */ +static void ggp_change_passwd_dialog(GaimConnection *gc) +{ + GaimRequestFields *fields; + GaimRequestFieldGroup *group; + GaimRequestField *field; + + GGPInfo *info = gc->proto_data; + GGPToken *token = info->token; + + char *msg; + + + fields = gaim_request_fields_new(); + group = gaim_request_field_group_new(NULL); + gaim_request_fields_add_group(fields, group); + + field = gaim_request_field_string_new("password_cur", + _("Current password"), "", FALSE); + gaim_request_field_string_set_masked(field, TRUE); + gaim_request_field_group_add_field(group, field); + + field = gaim_request_field_string_new("password1", + _("Password"), "", FALSE); + gaim_request_field_string_set_masked(field, TRUE); + gaim_request_field_group_add_field(group, field); + + field = gaim_request_field_string_new("password2", + _("Password (retype)"), "", FALSE); + gaim_request_field_string_set_masked(field, TRUE); + gaim_request_field_group_add_field(group, field); + + field = gaim_request_field_string_new("token", + _("Enter current token"), "", FALSE); + gaim_request_field_string_set_masked(field, FALSE); + gaim_request_field_group_add_field(group, field); + + /* original size: 60x24 */ + field = gaim_request_field_image_new("token_img", + _("Current token"), token->data, token->size); + gaim_request_field_group_add_field(group, field); + + msg = g_strdup_printf("%s %d", + _("Please, enter your current password and your new password for UIN: "), + ggp_get_uin(gaim_connection_get_account(gc))); + + gaim_request_fields(gc, + _("Change Gadu-Gadu Password"), + _("Change Gadu-Gadu Password"), + msg, + fields, _("OK"), G_CALLBACK(ggp_callback_change_passwd_ok), + _("Cancel"), NULL, gc); + + g_free(msg); +} +/* }}} */ + +/* + */ +/* static void ggp_change_passwd(GaimPluginAction *action) {{{ */ +static void ggp_change_passwd(GaimPluginAction *action) +{ + GaimConnection *gc = (GaimConnection *)action->context; + + ggp_token_request(gc, ggp_change_passwd_dialog); +} +/* }}} */ + +/* ----- CONFERENCES ---------------------------------------------------- */ + +/* + */ +/* static void ggp_callback_add_to_chat_ok(GaimConnection *gc, GaimRequestFields *fields) {{{ */ +static void ggp_callback_add_to_chat_ok(GaimConnection *gc, GaimRequestFields *fields) +{ + GGPInfo *info = gc->proto_data; + GaimRequestField *field; + const GList *sel; + + field = gaim_request_fields_get_field(fields, "name"); + sel = gaim_request_field_list_get_selected(field); + + ggp_confer_participants_add_uin(gc, sel->data, info->tmp_buddy); + info->tmp_buddy = 0; +} +/* }}} */ + +/* + */ +/* static void ggp_bmenu_add_to_chat(GaimBlistNode *node, gpointer ignored) {{{ */ +static void ggp_bmenu_add_to_chat(GaimBlistNode *node, gpointer ignored) +{ + GaimBuddy *buddy; + GaimConnection *gc; + GGPInfo *info; + + GaimRequestFields *fields; + GaimRequestFieldGroup *group; + GaimRequestField *field; + + GList *l; + gchar *msg; + + buddy = (GaimBuddy *)node; + gc = gaim_account_get_connection(gaim_buddy_get_account(buddy)); + info = gc->proto_data; + + /* TODO: It tmp_buddy != 0 then stop! */ + info->tmp_buddy = ggp_str_to_uin(gaim_buddy_get_name(buddy)); + + fields = gaim_request_fields_new(); + group = gaim_request_field_group_new(NULL); + gaim_request_fields_add_group(fields, group); + + field = gaim_request_field_list_new("name", "Chat name"); + for (l = info->chats; l != NULL; l = l->next) { + GGPChat *chat = l->data; + gaim_request_field_list_add(field, g_strdup(chat->name), + g_strdup(chat->name)); + } + gaim_request_field_group_add_field(group, field); + + msg = g_strdup_printf(_("Select a chat for buddy: %s"), + gaim_buddy_get_alias(buddy)); + gaim_request_fields(gc, + _("Add to chat..."), + _("Add to chat..."), + msg, + fields, + _("Add"), G_CALLBACK(ggp_callback_add_to_chat_ok), + _("Cancel"), NULL, gc); + g_free(msg); +} +/* }}} */ + +/* ----- BLOCK BUDDIES -------------------------------------------------- */ + +/* + */ +/* static void ggp_bmenu_block(GaimBlistNode *node, gpointer ignored) {{{ */ +static void ggp_bmenu_block(GaimBlistNode *node, gpointer ignored) +{ + GaimConnection *gc; + GaimBuddy *buddy; + GGPInfo *info; + uin_t uin; + + buddy = (GaimBuddy *)node; + gc = gaim_account_get_connection(gaim_buddy_get_account(buddy)); + info = gc->proto_data; + + uin = ggp_str_to_uin(gaim_buddy_get_name(buddy)); + + if (gaim_blist_node_get_bool(node, "blocked")) { + gaim_blist_node_set_bool(node, "blocked", FALSE); + gg_remove_notify_ex(info->session, uin, GG_USER_BLOCKED); + gg_add_notify_ex(info->session, uin, GG_USER_NORMAL); + gaim_debug_info("gg", "send: uin=%d; mode=NORMAL\n", uin); + } else { + gaim_blist_node_set_bool(node, "blocked", TRUE); + gg_remove_notify_ex(info->session, uin, GG_USER_NORMAL); + gg_add_notify_ex(info->session, uin, GG_USER_BLOCKED); + gaim_debug_info("gg", "send: uin=%d; mode=BLOCKED\n", uin); + } +} +/* }}} */ + +/* ---------------------------------------------------------------------- */ +/* ----- INTERNAL CALLBACKS --------------------------------------------- */ +/* ---------------------------------------------------------------------- */ + +/* just a prototype */ +static void ggp_set_status(GaimAccount *account, GaimStatus *status); + +/** + * Handle change of the status of the buddy. + * + * @param gc GaimConnection + * @param uin UIN of the buddy. + * @param status ID of the status. + * @param descr Description. + */ +/* static void ggp_generic_status_handler(GaimConnection *gc, uin_t uin, int status, const char *descr) {{{ */ +static void ggp_generic_status_handler(GaimConnection *gc, uin_t uin, + int status, const char *descr) +{ + gchar *from; + const char *st; + gchar *msg; + + from = g_strdup_printf("%ld", (unsigned long int)uin); + switch (status) { + case GG_STATUS_NOT_AVAIL: + case GG_STATUS_NOT_AVAIL_DESCR: + st = "offline"; + break; + case GG_STATUS_AVAIL: + case GG_STATUS_AVAIL_DESCR: + st = "available"; + break; + case GG_STATUS_BUSY: + case GG_STATUS_BUSY_DESCR: + st = "away"; + break; + case GG_STATUS_BLOCKED: + /* user is blocking us.... */ + st = "blocked"; + break; + default: + st = "available"; + gaim_debug_info("gg", + "GG_EVENT_NOTIFY: Unknown status: %d\n", status); + break; + } + + gaim_debug_info("gg", "st = %s\n", st); + msg = charset_convert(descr, "CP1250", "UTF-8"); + gaim_prpl_got_user_status(gaim_connection_get_account(gc), + from, st, "message", msg, NULL); + g_free(from); + g_free(msg); +} +/* }}} */ + +/* + */ +/* static void ggp_sr_close_cb(gpointer user_data) {{{ */ +static void ggp_sr_close_cb(gpointer user_data) +{ + GGPSearchForm *form = user_data; + GGPInfo *info = form->user_data; + + ggp_search_remove(info->searches, form->seq); + ggp_search_form_destroy(form); +} +/* }}} */ + +/** + * Translate a status' ID to a more user-friendly name. + * + * @param id The ID of the status. + * + * @return The user-friendly name of the status. + */ +/* static const char *ggp_status_by_id(unsigned int id) {{{ */ +static const char *ggp_status_by_id(unsigned int id) +{ + const char *st; + + gaim_debug_info("gg", "ggp_status_by_id: %d\n", id); + switch (id) { + case GG_STATUS_NOT_AVAIL: + st = _("Offline"); + break; + case GG_STATUS_AVAIL: + st = _("Available"); + break; + case GG_STATUS_BUSY: + st = _("Away"); + break; + default: + st = _("Unknown"); + break; + } + + return st; +} +/* }}} */ + +/* + */ +/* static void ggp_pubdir_handle_info(GaimConnection *gc, gg_pubdir50_t req, GGPSearchForm *form) {{{ */ +static void ggp_pubdir_handle_info(GaimConnection *gc, gg_pubdir50_t req, + GGPSearchForm *form) +{ + GString *text; + char *val, *who; + + text = g_string_new(""); + + val = ggp_search_get_result(req, 0, GG_PUBDIR50_STATUS); + /* XXX: Use of ggp_str_to_uin() is an ugly hack! */ + g_string_append_printf(text, "%s: %s
", + _("Status"), ggp_status_by_id(ggp_str_to_uin(val))); + g_free(val); + + who = ggp_search_get_result(req, 0, GG_PUBDIR50_UIN); + g_string_append_printf(text, "%s: %s
", + _("UIN"), who); + + val = ggp_search_get_result(req, 0, GG_PUBDIR50_FIRSTNAME); + g_string_append_printf(text, "%s: %s
", + _("First Name"), val); + g_free(val); + + val = ggp_search_get_result(req, 0, GG_PUBDIR50_NICKNAME); + g_string_append_printf(text, "%s: %s
", + _("Nickname"), val); + g_free(val); + + val = ggp_search_get_result(req, 0, GG_PUBDIR50_CITY); + g_string_append_printf(text, "%s: %s
", + _("City"), val); + g_free(val); + + val = ggp_search_get_result(req, 0, GG_PUBDIR50_BIRTHYEAR); + if (strncmp(val, "0", 1) == 0) { + g_free(val); + val = g_strdup(""); + } + g_string_append_printf(text, "%s: %s
", + _("Birth Year"), val); + g_free(val); + + val = ggp_buddy_get_name(gc, ggp_str_to_uin(who)); + g_free(who); + who = val; + + val = gaim_strdup_withhtml(text->str); + + gaim_notify_userinfo(gc, who, val, ggp_sr_close_cb, form); + + g_string_free(text, TRUE); + g_free(val); + g_free(who); +} +/* }}} */ + +/* + */ +/* static void ggp_pubdir_handle_full(GaimConnection *gc, gg_pubdir50_t req, GGPSearchForm *form) {{{ */ +static void ggp_pubdir_handle_full(GaimConnection *gc, gg_pubdir50_t req, + GGPSearchForm *form) +{ + GaimNotifySearchResults *results; + GaimNotifySearchColumn *column; + int res_count; + int start; + int i; + + g_return_if_fail(form != NULL); + + res_count = gg_pubdir50_count(req); + res_count = (res_count > PUBDIR_RESULTS_MAX) ? PUBDIR_RESULTS_MAX : res_count; + + results = gaim_notify_searchresults_new(); + + if (results == NULL) { + gaim_debug_error("gg", "ggp_pubdir_reply_handler: " + "Unable to display the search results.\n"); + gaim_notify_error(gc, NULL, + _("Unable to display the search results."), + NULL); + ggp_sr_close_cb(form); + return; + } + + column = gaim_notify_searchresults_column_new(_("UIN")); + gaim_notify_searchresults_column_add(results, column); + + column = gaim_notify_searchresults_column_new(_("First Name")); + gaim_notify_searchresults_column_add(results, column); + + column = gaim_notify_searchresults_column_new(_("Nickname")); + gaim_notify_searchresults_column_add(results, column); + + column = gaim_notify_searchresults_column_new(_("City")); + gaim_notify_searchresults_column_add(results, column); + + column = gaim_notify_searchresults_column_new(_("Birth Year")); + gaim_notify_searchresults_column_add(results, column); + + gaim_debug_info("gg", "Going with %d entries\n", res_count); + + start = (int)ggp_str_to_uin(gg_pubdir50_get(req, 0, GG_PUBDIR50_START)); + gaim_debug_info("gg", "start = %d\n", start); + + for (i = 0; i < res_count; i++) { + GList *row = NULL; + char *birth = ggp_search_get_result(req, i, GG_PUBDIR50_BIRTHYEAR); + + /* TODO: Status will be displayed as an icon. */ + /* row = g_list_append(row, ggp_search_get_result(req, i, GG_PUBDIR50_STATUS)); */ + row = g_list_append(row, ggp_search_get_result(req, i, + GG_PUBDIR50_UIN)); + row = g_list_append(row, ggp_search_get_result(req, i, + GG_PUBDIR50_FIRSTNAME)); + row = g_list_append(row, ggp_search_get_result(req, i, + GG_PUBDIR50_NICKNAME)); + row = g_list_append(row, ggp_search_get_result(req, i, + GG_PUBDIR50_CITY)); + row = g_list_append(row, + (birth && strncmp(birth, "0", 1)) ? birth : g_strdup("-")); + + gaim_notify_searchresults_row_add(results, row); + + if (i == res_count - 1) { + g_free(form->last_uin); + form->last_uin = ggp_search_get_result(req, i, GG_PUBDIR50_UIN); + } + } + + gaim_notify_searchresults_button_add(results, GAIM_NOTIFY_BUTTON_CONTINUE, + ggp_callback_show_next); + gaim_notify_searchresults_button_add(results, GAIM_NOTIFY_BUTTON_ADD, + ggp_callback_add_buddy); + gaim_notify_searchresults_button_add(results, GAIM_NOTIFY_BUTTON_IM, + ggp_callback_im); + + if (form->window == NULL) { + void *h = gaim_notify_searchresults(gc, + _("Gadu-Gadu Public Directory"), + _("Search results"), NULL, results, + (GaimNotifyCloseCallback)ggp_sr_close_cb, + form); + + if (h == NULL) { + gaim_debug_error("gg", "ggp_pubdir_reply_handler: " + "Unable to display the search results.\n"); + gaim_notify_error(gc, NULL, + _("Unable to display the search results."), + NULL); + return; + } + + form->window = h; + } else { + gaim_notify_searchresults_new_rows(gc, results, form->window); + } +} +/* }}} */ + +/* + */ +/* static void ggp_pubdir_reply_handler(GaimConnection *gc, gg_pubdir50_t req) {{{ */ +static void ggp_pubdir_reply_handler(GaimConnection *gc, gg_pubdir50_t req) +{ + GGPInfo *info = gc->proto_data; + GGPSearchForm *form; + int res_count; + guint32 seq; + + seq = gg_pubdir50_seq(req); + form = ggp_search_get(info->searches, seq); + + /* + * this can happen when user will request more results + * and close the results window before they arrive. + */ + g_return_if_fail(form != NULL); + + res_count = gg_pubdir50_count(req); + if (res_count < 1) { + gaim_debug_info("gg", "GG_EVENT_PUBDIR50_SEARCH_REPLY: Nothing found\n"); + gaim_notify_error(gc, NULL, + _("No matching users found"), + _("There are no users matching your search criteria.")); + ggp_sr_close_cb(form); + return; + } + + switch (form->search_type) { + case GGP_SEARCH_TYPE_INFO: + ggp_pubdir_handle_info(gc, req, form); + break; + case GGP_SEARCH_TYPE_FULL: + ggp_pubdir_handle_full(gc, req, form); + break; + default: + gaim_debug_warning("gg", "Unknown search_type!\n"); + break; + } +} +/* }}} */ + +/** + * Dispatch a message received from a buddy. + * + * @param gc GaimConnection. + * @param ev Gadu-Gadu event structure. + */ +/* static void ggp_recv_message_handler(GaimConnection *gc, const struct gg_event *ev) {{{ */ +static void ggp_recv_message_handler(GaimConnection *gc, const struct gg_event *ev) +{ + GGPInfo *info = gc->proto_data; + GaimConversation *conv; + gchar *from; + gchar *msg; + gchar *tmp; + + from = g_strdup_printf("%lu", (unsigned long int)ev->event.msg.sender); + + tmp = charset_convert((const char *)ev->event.msg.message, + "CP1250", "UTF-8"); + gaim_str_strip_char(tmp, '\r'); + msg = g_markup_escape_text(tmp, -1); + g_free(tmp); + + gaim_debug_info("gg", "msg form (%s): %s (class = %d; rcpt_count = %d)\n", + from, msg, ev->event.msg.msgclass, + ev->event.msg.recipients_count); + + if (ev->event.msg.recipients_count == 0) { + serv_got_im(gc, from, msg, 0, ev->event.msg.time); + } else { + const char *chat_name; + int chat_id; + char *buddy_name; + + chat_name = ggp_confer_find_by_participants(gc, + ev->event.msg.recipients, + ev->event.msg.recipients_count); + + if (chat_name == NULL) { + chat_name = ggp_confer_add_new(gc, NULL); + serv_got_joined_chat(gc, info->chats_count, chat_name); + + ggp_confer_participants_add_uin(gc, chat_name, + ev->event.msg.sender); + + ggp_confer_participants_add(gc, chat_name, + ev->event.msg.recipients, + ev->event.msg.recipients_count); + } + conv = ggp_confer_find_by_name(gc, chat_name); + chat_id = gaim_conv_chat_get_id(GAIM_CONV_CHAT(conv)); + + buddy_name = ggp_buddy_get_name(gc, ev->event.msg.sender); + serv_got_chat_in(gc, chat_id, buddy_name, + GAIM_MESSAGE_RECV, msg, ev->event.msg.time);