pidgin/purple-plugin-pack
Clone
propagate from branch 'org.guifications.plugins' (head f09d18bf539ac5277d82c5b0c94692107074a0bb)
to branch 'org.guifications.plugins.smartear' (head 22d2b0f5359de38829e576dcd53b00bc1e4fb3f8)
--- a/.mtn-ignore Tue Oct 09 06:21:19 2007 -0400
+++ b/.mtn-ignore Mon Mar 24 05:08:00 2008 -0400
@@ -4,7 +4,9 @@
-config.(guess|log|status|sub)
+buddytime/gtktimezonetest +config.(cache|guess|log|status|sub) @@ -14,6 +16,8 @@
--- a/AUTHORS Tue Oct 09 06:21:19 2007 -0400
+++ b/AUTHORS Mon Mar 24 05:08:00 2008 -0400
@@ -9,6 +9,10 @@
Daniel Atallah <datallah@users.sourceforge.net>
Sadrul H Chowdhury <sadrul@users.sourceforge.net>
Richard Laager <rlaager@guifications.org>
+Martijn van Oosterhout <kleptog@svana.org> +Matt Perry <guy@fscked.org> +Andrew Pangborn <gaim@andrewpangborn.com> @@ -36,15 +40,16 @@
-qwert - realphabetize or replace with real name before release!
--- a/ChangeLog Tue Oct 09 06:21:19 2007 -0400
+++ b/ChangeLog Mon Mar 24 05:08:00 2008 -0400
@@ -1,13 +1,41 @@
- * Merged autorejoin into irc-more. No prefs migration will take place.
- You will need to reconfigure the delay yourself. Autorejoin no longer
- * Slashexec's '/exec command' and '!command' are now optional (QuLogic)
- * Listhandler now supports restoring a buddy list from a backed-up
- blist.xml file created by backing up ~/.purple.
- * Added support for initial user modes to irc-more
+ * Merged the Autoprofile plugin into our build system. + * Fixed convbadger's failure to update on conversation switch. + * Fixed a typo in irc-more's source that allowed a potential double-free + * Fixed unregistering commands when unloading gRIM and Magic 8 ball plugin + * Fixed napster plugin. It builds cleanly and loads properly now. + * Added /bollocks command to Magic 8 ball plugin inspired by (and ported + from) /dev/bollocks kernel module + * Added Andrew Pangborn's Enhanced History plugin and ported it to the + Pidgin and libpurple APIs. Cleaned up some preferences as well. + * Dice plugin now supports dice notation + (http://en.wikipedia.org/wiki/Dice_notation), but it's not perfect yet. + * Dice plugin now calls the /me command with its output. + * Irc-more plugin adds notice support only when built against libpurple + * Napster plugin now builds by default. + * Removed the broadcast plugin. + * Fixed the --with-plugins configure argument. It now correctly handles + all, default, and a comma separated list of plugins. + * Fixed a crash in the xchat-chats plugin which occurs due to the changes + to the conversation window in 2.4.0. + * Fixed a missing header include in the timelog plugin which caused a + plugin load failure under some circumstances. + * Finally added some content to README + * Added 'menuconfig' script to make it easier to select what plugins to + * Added support for initial setting/unsetting of user modes to irc-more * Added the abusive findip plugin
+ * Added dewysiwygification plugin + * Added timelog plugin, from Jon Oberheide's gaim-timelog + * Fixed autoreply so it doesn't reply with an empty message (rageboy04) * Fixed a crash in ignore where a nickname that is not all lowercase
causes unintended behavior resulting in a crash (rageboy04, qwert)
* Fixed a possible double-free crash in /notice support in irc-more.
@@ -19,7 +47,16 @@
* Fixed building with ancient glib. (Bodo Bellut)
* Removed the .build file from hideconv to remove it from default
- builds. Pidgin 2.2.1 will have persistent conversations.
+ builds. Pidgin will have persistent conversations soon. + * Partially merged buddytimezone from the buddytools package into the + existing (incomplete) buddytime plugin + * Autoreply now can be disabled per-account (rageboy04) + * Listhandler now supports restoring a buddy list from a backed-up + blist.xml file created by backing up ~/.purple. + * Merged autorejoin into irc-more. No prefs migration will take place. + You will need to reconfigure the delay yourself. Autorejoin no longer + * Slashexec's '/exec command' and '!command' are now optional (QuLogic) * Fixed lack of .build, .pidgin-plugin, and Makefile.mingw for convbadger
--- a/Makefile.am Tue Oct 09 06:21:19 2007 -0400
+++ b/Makefile.am Mon Mar 24 05:08:00 2008 -0400
@@ -44,6 +44,9 @@
+ echo $(DIST_ARCHIVES) | xargs -n 1 gpg -a -b @echo "---------------------------------------"
--- a/README Tue Oct 09 06:21:19 2007 -0400
+++ b/README Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,9 @@
+The Purple Plugin Pack was originally created by Gary Kramlich and Stu +Tomlinson as a way to distribute their ever growing lists of simple Pidgin +plugins. It has since grown from its origins of about 6 plugins to nearly 50. +Also, many more developers have continued to add to it, including John Bailey, +Peter Lawler, Sadrul Habib Chowdhury, and most recently Richard Laager. +More information on the Plugin Pack can be found at +http://plugins.guifications.org/trac/wiki/PluginPack --- a/VERSION Tue Oct 09 06:21:19 2007 -0400
+++ b/VERSION Mon Mar 24 05:08:00 2008 -0400
@@ -1,1 +1,1 @@
--- a/album/album-ui.c Tue Oct 09 06:21:19 2007 -0400
+++ b/album/album-ui.c Mon Mar 24 05:08:00 2008 -0400
@@ -1,8 +1,8 @@
* Album (Buddy Icon Archiver)
- * Copyright (C) 2005-2006, Sadrul Habib Chowdhury <imadil@gmail.com>
- * Copyright (C) 2005-2006, Richard Laager <rlaager@pidgin.im>
+ * Copyright (C) 2005-2008, Sadrul Habib Chowdhury <imadil@gmail.com> + * Copyright (C) 2005-2008, Richard Laager <rlaager@pidgin.im> * Copyright (C) 2006, Jérôme Poulin (TiCPU) <jeromepoulin@gmail.com>
* This program is free software; you can redistribute it and/or
@@ -126,6 +126,13 @@
static void update_icon_view(icon_viewer_key *key);
static void show_buddy_icon_window(icon_viewer_key *key, const char *name);
+void icon_viewer_key_free(void *data) + icon_viewer_key *key = (icon_viewer_key *)data; + g_free(key->screenname); guint icon_viewer_hash(gconstpointer data)
const icon_viewer_key *key = data;
@@ -932,6 +939,7 @@
/* Return if a window is already opened for the buddy. */
if ((bw = g_hash_table_lookup(buddy_windows, key)) != NULL)
+ icon_viewer_key_free(key); gtk_window_present(GTK_WINDOW(bw->window));
@@ -942,6 +950,7 @@
if (key->contact == NULL &&
(bw = g_hash_table_find(buddy_windows, (GHRFunc)compare_buddy_keys, key)) != NULL)
+ icon_viewer_key_free(key); gtk_window_present(GTK_WINDOW(bw->window));
--- a/album/album-ui.h Tue Oct 09 06:21:19 2007 -0400
+++ b/album/album-ui.h Mon Mar 24 05:08:00 2008 -0400
@@ -1,8 +1,8 @@
* Album (Buddy Icon Archiver)
- * Copyright (C) 2005-2006, Sadrul Habib Chowdhury <imadil@gmail.com>
- * Copyright (C) 2005-2006, Richard Laager <rlaager@pidgin.im>
+ * Copyright (C) 2005-2008, Sadrul Habib Chowdhury <imadil@gmail.com> + * Copyright (C) 2005-2008, Richard Laager <rlaager@pidgin.im> * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
@@ -36,6 +36,8 @@
gboolean icon_viewer_equal(gconstpointer y, gconstpointer z);
+void icon_viewer_key_free(void *key); GList *album_get_plugin_actions(PurplePlugin *plugin, gpointer data);
void album_blist_node_menu_cb(PurpleBlistNode *node, GList **menu);
--- a/album/album.c Tue Oct 09 06:21:19 2007 -0400
+++ b/album/album.c Mon Mar 24 05:08:00 2008 -0400
@@ -1,8 +1,8 @@
* Album (Buddy Icon Archiver)
- * Copyright (C) 2005-2006, Sadrul Habib Chowdhury <imadil@gmail.com>
- * Copyright (C) 2005-2006, Richard Laager <rlaager@pidgin.im>
+ * Copyright (C) 2005-2008, Sadrul Habib Chowdhury <imadil@gmail.com> + * Copyright (C) 2005-2008, Richard Laager <rlaager@pidgin.im> * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
@@ -253,7 +253,7 @@
cache_existing_icons(NULL);
- buddy_windows = g_hash_table_new_full(icon_viewer_hash, icon_viewer_equal, g_free, g_free);
+ buddy_windows = g_hash_table_new_full(icon_viewer_hash, icon_viewer_equal, icon_viewer_key_free, g_free); --- a/album/album.h Tue Oct 09 06:21:19 2007 -0400
+++ b/album/album.h Mon Mar 24 05:08:00 2008 -0400
@@ -1,8 +1,8 @@
* Album (Buddy Icon Archiver)
- * Copyright (C) 2005-2006, Sadrul Habib Chowdhury <imadil@gmail.com>
- * Copyright (C) 2005-2006, Richard Laager <rlaager@pidgin.im>
+ * Copyright (C) 2005-2008, Sadrul Habib Chowdhury <imadil@gmail.com> + * Copyright (C) 2005-2008, Richard Laager <rlaager@pidgin.im> * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
@@ -22,6 +22,7 @@
+/* If you can't figure out what this line is for, DON'T TOUCH IT. */ #include "../common/pp_internal.h"
--- a/autogen.sh Tue Oct 09 06:21:19 2007 -0400
+++ b/autogen.sh Mon Mar 24 05:08:00 2008 -0400
@@ -1,49 +1,135 @@
-PACKAGE="purple-plugin_pack"
-(intltoolize --version) < /dev/null > /dev/null 2>&1 || {
- echo "You must have intltool installed to compile $PACKAGE";
+# Guifications - The end-all, be-all notification framework +# Copyright (C) 2003-2008 Gary Kramlich <grim@reaperworld.com> +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 2 of the License, or (at your option) +# 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 +# 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., 51 +# Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -(libtoolize --version) < /dev/null > /dev/null 2>&1 || {
- echo "You must have libtool installed to compile $PACKAGE";
+############################################################################### +############################################################################### +# This script uses a config file that can be used to stash common arguments +# passed to configure or environment variables that need to be set before +# configure is called. The configuration file is a simple shell script that +# By default, the config file that is used is named 'autogen.args'. This can +# Available options that are handled are as follow: +# ACLOCAL_FLAGS - command line arguments to pass to aclocal +# AUTOCONF_FLAGS - command line arguments to pass to autoconf +# AUTOHEADER_FLAGS - command line arguments to pass to autoheader +# AUTOMAKE_FLAGS - command line arguments to pass to automake flags +# CONFIGURE_FLAGS - command line arguments to pass to configure +# INTLTOOLIZE_FLAGS - command line arguments to pass to intltoolize +# LIBTOOLIZE_FLAGS - command line arguments to pass to libtoolize +# If you're using a different c compiler, you can override the environment +# variable in 'autogen.args'. For example, say you're using distcc, just add +# the following to 'autogen.args': +# This will work for any influential environment variable to configure. +############################################################################### +PACKAGE="Purple Plugin Pack" +ARGS_FILE="autogen.args" -(automake --version) < /dev/null > /dev/null 2>&1 || {
- echo "You must have automake installed to compile $PACKAGE";
+############################################################################### +############################################################################### + echo -n "checking for ${CMD}... " + if [ x"${BIN}" = x"" ] ; then + echo "${CMD} is required to build ${PACKAGE}!" -(autoconf --version) < /dev/null > /dev/null 2>&1 || {
- echo "You must have autoconf installed to compile $PACKAGE";
+run_or_die () { # beotch + echo -n "running ${CMD} ${@}... " + OUTPUT=`${CMD} ${@} 2>&1` + if [ x"${OUTPUT}" != x"" ] ; then -echo "Generating configuration files for $PACKAGE, please wait...."
+############################################################################### +# We really start here, yes, very sneaky! +############################################################################### +if [ x"${FIGLET}" != x"" ] ; then + ${FIGLET} -f small ${PACKAGE} + echo "build system is being generated" + echo "autogenerating build system for '${PACKAGE}'" -echo "Running libtoolize, please ignore non-fatal messages...."
-echo n | libtoolize --copy --force || exit;
+############################################################################### +# Look for our args file +############################################################################### +echo -n "checking for ${ARGS_FILE}: " +if [ -f ${ARGS_FILE} ] ; then + echo -n "sourcing ${ARGS_FILE}: " -libtoolize -c -f --automake
-intltoolize --force --copy
-automake --add-missing --copy
+############################################################################### +# Check for our required helpers +############################################################################### +check "libtoolize"; LIBTOOLIZE=${BIN}; +check "intltoolize"; INTLTOOLIZE=${BIN}; +check "aclocal"; ACLOCAL=${BIN}; +check "autoheader"; AUTOHEADER=${BIN}; +check "automake"; AUTOMAKE=${BIN}; +check "autoconf"; AUTOCONF=${BIN}; -echo "Running ./configure $@"
+############################################################################### +# Run all of our helpers +############################################################################### +run_or_die ${LIBTOOLIZE} -c -f --automake ${LIBTOOLIZE_FLAGS} +run_or_die ${INTLTOOLIZE} -c -f --automake ${INTLTOOLIZE_FLAGS} +run_or_die ${ACLOCAL} -I m4 ${ACLOCAL_FLAGS} +run_or_die ${AUTOHEADER} ${AUTOHEADER_FLAGS} +run_or_die ${AUTOMAKE} -a -c -f --gnu ${AUTOMAKE_FLAGS} +run_or_die ${AUTOCONF} -f ${AUTOCONF_FLAGS} +############################################################################### +############################################################################### +echo "running ./configure ${CONFIGURE_ARGS} $@" +./configure ${CONFIGURE_ARGS} $@
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/autoprofile/Makefile.am Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,60 @@
+autoprofiledir = $(PURPLE_LIBDIR) +autoprofile_la_LDFLAGS = -module -avoid-version +autoprofile_LTLIBRARIES = autoprofile.la +autoprofile_la_SOURCES = \ +autoprofile_la_LIBADD = \ + -DLIBDIR=\"$(PIDGIN_LIBDIR)\" \ + -DDATADIR=\"$(PIDGIN_DATADIR)\" \ + -DPIXMAPSDIR=\"$(PIDGIN_PIXMAPSDIR)\" \ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/autoprofile/Makefile.mingw Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,37 @@
+# Description: Makefile for autoprofile plugin. +include $(PP_TOP)/win_pp.mak --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/autoprofile/autoaway.c Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,145 @@
+/*--------------------------------------------------------------------------* + * A Purple away message and profile manager that supports dynamic text * + * AutoProfile is the legal property of its developers. 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 "autoprofile.h" +#include "conversation.h" +#define AP_IDLE_CHECK_INTERVAL 5 +static guint check_timeout = 0; +static guint pref_cb = 0; +static time_t last_active_time = 0; +static gboolean is_idle () + PurpleIdleUiOps *ui_ops; + const gchar *idle_reporting; + ui_ops = purple_idle_get_ui_ops (); + idle_reporting = purple_prefs_get_string ("/core/away/idle_reporting"); + if (!strcmp (idle_reporting, "system") && + (ui_ops != NULL) && (ui_ops->get_time_idle != NULL)) { + time_idle = time (NULL) - last_active_time; + } else if (!strcmp (idle_reporting, "gaim")) { + time_idle = time (NULL) - last_active_time; + (60 * purple_prefs_get_int("/core/away/mins_before_away"))); +static gboolean ap_check_idleness (gpointer data) + // 0 0 0 don't do anything + // 0 0 1 ap_use_idleaway () + // 1 0 x don't do anything, we're already away + // 1 1 0 ap_dont_use_idleaway () + // 1 1 1 don't do anything + if (ap_is_currently_away () && !ap_autoaway_in_use ()) return TRUE; + auto_away = purple_prefs_get_bool ( + "/plugins/gtk/autoprofile/away_when_idle"); + if (auto_away && !ap_is_currently_away () && !ap_autoaway_in_use ()) { + if (ap_is_currently_away () && ap_autoaway_in_use ()) { + ap_autoaway_disable (); +void ap_autoaway_touch () + time (&last_active_time); +static gboolean writing_im_msg_cb (PurpleAccount *account, const char *who, + char **message, PurpleConversation *conv, PurpleMessageFlags flags) + ap_check_idleness (NULL); +static void auto_pref_cb ( + const char *name, PurplePrefType type, gconstpointer val, gpointer data) + if (!purple_prefs_get_bool ("/core/away/away_when_idle")) return; + purple_notify_error (NULL, NULL, + N_("This preference is disabled"), + N_("This preference currently has no effect because AutoProfile is in " + "use. To modify this behavior, use the AutoProfile configuration " + purple_prefs_set_bool ("/core/away/away_when_idle", FALSE); +/*--------------------------------------------------------------------------* + * Global functions to start it all * + *--------------------------------------------------------------------------*/ +void ap_autoaway_start () + purple_prefs_set_bool ("/core/away/away_when_idle", FALSE); + check_timeout = purple_timeout_add (AP_IDLE_CHECK_INTERVAL * 1000, + ap_check_idleness, NULL); + purple_signal_connect (purple_conversations_get_handle (), "writing-im-msg", + ap_get_plugin_handle (), PURPLE_CALLBACK(writing_im_msg_cb), NULL); + pref_cb = purple_prefs_connect_callback (ap_get_plugin_handle (), + "/core/away/away_when_idle", auto_pref_cb, NULL); +void ap_autoaway_finish () + // Assumes signals are disconnected globally + purple_prefs_disconnect_callback (pref_cb); + if (check_timeout > 0) purple_timeout_remove (check_timeout); + purple_prefs_set_bool ("/core/away/away_when_idle", + purple_prefs_get_bool ("/plugins/gtk/autoprofile/away_when_idle")); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/autoprofile/autoprofile.c Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,861 @@
+/*--------------------------------------------------------------------------* + * A Purple away message and profile manager that supports dynamic text * + * AutoProfile is the legal property of its developers. 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 "autoprofile.h" +#include "savedstatuses.h" +static void ap_status_changed ( + const char *, PurplePrefType, gconstpointer, gpointer); +static void ap_account_connected (PurpleConnection *); +static void ap_delete_legacy_prefs (); +static void ap_update_queue_start (); +static void ap_update_queue_finish (); +/*-------------------------------------------------------------------------- + *------------------------------------------------------------------------*/ +static PurplePlugin *plugin_handle = NULL; +static PurpleSavedStatus *current_ap_status = NULL; +static GStaticMutex update_timeout_mutex = G_STATIC_MUTEX_INIT; +static GHashTable *update_timeouts = NULL; +static gboolean using_idleaway = FALSE; +static GStaticMutex update_queue_mutex = G_STATIC_MUTEX_INIT; +static GList *queued_profiles = NULL; +static guint update_queue_timeout = 0; +/* Functions related to general variables */ +PurplePlugin *ap_get_plugin_handle () { return plugin_handle; } +gboolean ap_is_currently_away () { + return current_ap_status != NULL && + purple_savedstatus_get_type (current_ap_status) == PURPLE_STATUS_AWAY; +/*-------------------------------------------------------------------------- + * REQUIRED GAIM FUNCTIONS- INFO, INITIALIZATION, UNLOADING + *------------------------------------------------------------------------*/ +/* What to do when plugin is loaded */ +static gboolean plugin_load (PurplePlugin *plugin) + ap_debug ("general", "AutoProfile is being loaded"); + plugin_handle = plugin; + current_ap_status = purple_savedstatus_new (NULL, PURPLE_STATUS_UNSET); + update_timeouts = g_hash_table_new (NULL, NULL); + ap_delete_legacy_prefs (); + /* The core autoprofile tracking system */ + purple_prefs_connect_callback (plugin_handle, "/core/savedstatus/current", + ap_status_changed, NULL); + purple_signal_connect (purple_connections_get_handle (), + "signed-on", plugin_handle, + PURPLE_CALLBACK (ap_account_connected), NULL); + accounts_pref = purple_prefs_get_string_list ( + "/plugins/gtk/autoprofile/profile_accounts"); + ap_gtk_set_progress_visible (AP_UPDATE_PROFILE, (accounts_pref != NULL)); + free_string_list (accounts_pref); + ap_update_after_delay (AP_UPDATE_STATUS); + ap_update_after_delay (AP_UPDATE_PROFILE); + ap_update_queue_start (); +/* What to do when plugin is unloaded */ +static gboolean plugin_unload (PurplePlugin *plugin) + ap_update_queue_finish (); + ap_autoreply_finish (); + using_idleaway = FALSE; + ap_update_stop (AP_UPDATE_STATUS); + ap_update_stop (AP_UPDATE_PROFILE); + /* Disconnect tracking system */ + purple_signals_disconnect_by_handle (plugin); + ap_component_finish (); + g_hash_table_destroy (update_timeouts); +/* General information */ +static PurplePluginInfo info = + PURPLE_PLUGIN_STANDARD, /* type */ + PIDGIN_PLUGIN_TYPE, /* ui_requirement */ + NULL, /* dependencies */ + PURPLE_PRIORITY_DEFAULT, /* priority */ + N_("gtk-kluge-autoprofile"), /* id */ + N_("AutoProfile"), /* name */ + PP_VERSION, /* version */ + N_("User profile and status message content generator"),/* summary */ + N_("Allows user to place dynamic text into profiles\n" + "and status messages, with the text automatically\n" + "updated whenever content changes"), + N_("Casey Ho <casey at hkn-berkeley-edu>" + "\n\t\t\taim:caseyho"), + N_("http://autoprofile.sourceforge.net/"), /* homepage */ + plugin_load, /* load */ + plugin_unload, /* unload */ + &ui_info, /* ui_info */ +/*-------------------------------------------------------------------------- + *------------------------------------------------------------------------*/ +static gint get_max_size_status ( + const PurpleAccount *account, const PurpleStatusPrimitive type) { + case PURPLE_STATUS_AVAILABLE: return AP_SIZE_AVAILABLE_MAX; + case PURPLE_STATUS_AWAY: return AP_SIZE_AWAY_MAX; + default: return AP_SIZE_MAXIMUM; + id = purple_account_get_protocol_id (account); + case PURPLE_STATUS_AVAILABLE: + if (!strcmp (id, "prpl-oscar")) return AP_SIZE_AVAILABLE_AIM; + else if (!strcmp (id, "prpl-msn")) return AP_SIZE_AVAILABLE_MSN; + else if (!strcmp (id, "prpl-yahoo")) return AP_SIZE_AVAILABLE_YAHOO; + else return AP_SIZE_AVAILABLE_MAX; + case PURPLE_STATUS_AWAY: + if (!strcmp (id, "prpl-oscar")) return AP_SIZE_AWAY_AIM; + else return AP_SIZE_AWAY_MAX; + return AP_SIZE_MAXIMUM; +static const char *ap_savedstatus_get_message ( + const PurpleSavedStatus *status, const PurpleAccount *account) + const PurpleSavedStatusSub *substatus; + substatus = purple_savedstatus_get_substatus(status, account); + if (substatus != NULL) { + return purple_savedstatus_substatus_get_message (substatus); + return purple_savedstatus_get_message (status); +static PurpleStatusPrimitive ap_savedstatus_get_type ( + const PurpleSavedStatus *status, const PurpleAccount *account) + const PurpleSavedStatusSub *substatus; + substatus = purple_savedstatus_get_substatus(status, account); + if (substatus != NULL) { + return purple_status_type_get_primitive ( + purple_savedstatus_substatus_get_type (substatus)); + return purple_savedstatus_get_type (status); +gchar *ap_get_sample_status_message (PurpleAccount *account) + const PurpleSavedStatus *s; + PurpleStatusPrimitive type; + s = (using_idleaway? purple_savedstatus_get_idleaway () : + purple_savedstatus_get_current ()); + message = ap_savedstatus_get_message (s, account); + type = ap_savedstatus_get_type (s, account); + if (!message) return NULL; + return ap_generate (message, get_max_size_status (account, type)); +static gchar *ap_process_replacement (const gchar *f) { + w = ap_widget_find (f); + result = w->component->generate (w); + g_string_printf (s, "[%s]", f); + g_string_free (s, FALSE); +/* The workhorse generation function! */ +gchar *ap_generate (const gchar *f, gint max_length) { + gchar *format, *format_start, *percent_start; + output = g_string_new (""); + format_start = format = purple_utf8_salvage (f); + /* When a % has been read (and searching for next %), state is 1 + g_string_append_unichar (output, g_utf8_get_char ("[")); + g_string_append (output, percent_start); + percent_start = format = format+1; + } else if (*format == ']') { + replacement = ap_process_replacement (percent_start); + g_string_append (output, replacement); + format = g_utf8_next_char (format); + g_string_append (output, "<br>"); + } else if (*format == '[') { + percent_start = format+1; + g_string_append_unichar (output, g_utf8_get_char (format)); + format = g_utf8_next_char (format); + /* Deal with case where final ] not found */ + g_string_append_unichar (output, g_utf8_get_char ("[")); + g_string_append (output, percent_start); + g_string_truncate (output, max_length); + result = purple_utf8_salvage(output->str); + g_string_free (output, TRUE); +void ap_account_enable_profile (const PurpleAccount *account, gboolean enable) { + gboolean original_status; + gchar *username, *protocol_id; + original_status = ap_account_has_profile_enabled (account); + if (original_status == enable) { + ap_debug_warn ("profile", "New status identical to original, skipping"); + original = purple_prefs_get_string_list ( + "/plugins/gtk/autoprofile/profile_accounts"); + username = strdup (purple_account_get_username (account)); + protocol_id = strdup (purple_account_get_protocol_id (account)); + /* Remove from the list */ + ap_debug ("profile", "Disabling profile updates for account"); + if (!strcmp (original->data, username) && + !strcmp (original->next->data, protocol_id)) { + original = original->next->next; + original = original->next->next; + node->next->next = ret; + GList *ret_start, *ret_end; + ap_debug ("profile", "enabling profile updates for account"); + ret_start = (GList *) malloc (sizeof (GList)); + ret_end = (GList *) malloc (sizeof (GList)); + ret_start->data = username; + ret_start->next = ret_end; + ret_end->data = protocol_id; + ret_end->next = original; + purple_prefs_set_string_list ( + "/plugins/gtk/autoprofile/profile_accounts", new); + ap_gtk_set_progress_visible (AP_UPDATE_PROFILE, (new != NULL)); + free_string_list (new); +gboolean ap_account_has_profile_enabled (const PurpleAccount *account) { + GList *accounts_list, *start_list; + accounts_list = purple_prefs_get_string_list ( + "/plugins/gtk/autoprofile/profile_accounts"); + start_list = accounts_list; + /* Search through list of values */ + while (accounts_list) { + // Make sure these things come in pairs + if (accounts_list->next == NULL) { + ap_debug_error ("is_account_profile_enabled", "invalid account string"); + free_string_list (start_list); + if (!strcmp ((char *) accounts_list->data, account->username)) { + if (!strcmp ((char *) accounts_list->next->data, account->protocol_id)) + free_string_list (start_list); + accounts_list = accounts_list->next->next; + /* Not found, hence it wasn't enabled */ + free_string_list (start_list); +/* Profiles: Update every so often */ +static gboolean ap_update_profile () { + PurpleAccount *account; + const GList *purple_accounts; + gboolean account_updated; + char *generated_profile; + /* Generate the profile text */ + format = purple_prefs_get_string ("/plugins/gtk/autoprofile/profile"); + ap_debug_error ("general", "profile is null"); + generated_profile = ap_generate (format, AP_SIZE_PROFILE_MAX); + // If string is blank, nothing would happen + if (*generated_profile == '\0') { + free (generated_profile); + ap_debug_misc ("general", "empty profile set"); + generated_profile = strdup (" "); + /* Get all accounts and search through each */ + account_updated = FALSE; + for (purple_accounts = purple_accounts_get_all (); + purple_accounts != NULL; + purple_accounts = purple_accounts->next) { + account = (PurpleAccount *)purple_accounts->data; + old_info = purple_account_get_user_info (account); + /* Check to see if update option set on account */ + if (ap_account_has_profile_enabled (account) && + (old_info == NULL || strcmp (old_info, generated_profile))) { + purple_account_set_user_info (account, generated_profile); + account_updated = TRUE; + if (purple_account_is_connected (account)) { + g_static_mutex_lock (&update_queue_mutex); + if (g_list_find (queued_profiles, account) == NULL) { + queued_profiles = g_list_append (queued_profiles, account); + g_static_mutex_unlock (&update_queue_mutex); + ap_debug_misc ("general", "account not online, not setting profile"); + ap_gtk_add_message (AP_UPDATE_PROFILE, AP_MESSAGE_TYPE_PROFILE, + free (generated_profile); + return account_updated; +static gboolean ap_update_status () + const PurpleSavedStatus *template_status; + GHashTable *substatus_messages; + gchar *new_message, *new_substatus_message; + const gchar *sample_message, *old_message; + PurpleStatusPrimitive old_type, new_type; + const PurpleStatusType *substatus_type; + PurpleAccount *account; + PurpleSavedStatusSub *substatus; + template_status = (using_idleaway? purple_savedstatus_get_idleaway () : + purple_savedstatus_get_current ()); + /* If there are substatuses */ + if (purple_savedstatus_has_substatuses (template_status)) { + substatus_messages = g_hash_table_new (NULL, NULL); + for (accounts = purple_accounts_get_all (); + accounts = accounts->next) + account = (PurpleAccount *) accounts->data; + substatus = purple_savedstatus_get_substatus (template_status, account); + new_type = purple_status_type_get_primitive ( + purple_savedstatus_substatus_get_type (substatus)); + purple_savedstatus_substatus_get_message (substatus); + new_substatus_message = ap_generate (sample_message, + get_max_size_status (account, new_type)); + new_substatus_message = NULL; + g_hash_table_insert (substatus_messages, account, + new_substatus_message); + old_type = ap_savedstatus_get_type (current_ap_status, account); + ap_savedstatus_get_message (current_ap_status, account); + if ((old_type != new_type) || + ((old_message == NULL || new_substatus_message == NULL) && + (old_message != new_substatus_message)) || + (old_message != NULL && new_substatus_message != NULL && + strcmp (old_message, new_substatus_message))) + substatus_messages = NULL; + /* And then the generic main message */ + sample_message = purple_savedstatus_get_message (template_status); + new_message = ap_generate (sample_message, get_max_size_status (NULL, + purple_savedstatus_get_type (template_status))); + new_type = purple_savedstatus_get_type (template_status); + old_type = purple_savedstatus_get_type (current_ap_status); + old_message = purple_savedstatus_get_message (current_ap_status); + if ((old_type != new_type) || + ((old_message == NULL || new_message == NULL) && + (old_message != new_message)) || + (old_message != NULL && new_message != NULL && + strcmp (old_message, new_message))) + PurpleSavedStatus *new_status; + new_status = purple_savedstatus_new (NULL, new_type); + purple_savedstatus_set_message (new_status, new_message); + for (accounts = purple_accounts_get_all (); + accounts = accounts->next) { + account = (PurpleAccount *) accounts->data; + substatus = purple_savedstatus_get_substatus (template_status, account); + if (substatus != NULL) { + substatus_type = purple_savedstatus_substatus_get_type (substatus); + new_substatus_message = (gchar *) + g_hash_table_lookup (substatus_messages, account); + purple_savedstatus_set_substatus ( + new_status, account, substatus_type, new_substatus_message); + free (new_substatus_message); + purple_savedstatus_activate_for_account (new_status, account); + current_ap_status = new_status; + if (new_type == PURPLE_STATUS_AVAILABLE) type = AP_MESSAGE_TYPE_AVAILABLE; + else if (new_type == PURPLE_STATUS_AWAY) type = AP_MESSAGE_TYPE_AWAY; + else type = AP_MESSAGE_TYPE_STATUS; + ap_gtk_add_message (AP_UPDATE_STATUS, type, new_message); + if (new_message) free (new_message); + if (substatus_messages) { + g_hash_table_destroy (substatus_messages); +static gboolean ap_update_cb (gpointer data) { + g_static_mutex_lock (&update_timeout_mutex); + /* Start by removing timeout to self no matter what */ + timeout = GPOINTER_TO_INT (g_hash_table_lookup (update_timeouts, data)); + if (timeout) purple_timeout_remove (timeout); + /* In future, check here if widget content has changed? */ + switch (GPOINTER_TO_INT (data)) { + result = ap_update_status (); + case AP_UPDATE_PROFILE: + result = ap_update_profile (); + ap_debug ("general", "Content hasn't changed, updating later"); + delay = AP_SCHEDULE_UPDATE_DELAY; + ap_debug ("general", "Content updated"); + purple_prefs_get_int ("/plugins/gtk/autoprofile/delay_update") * 1000; + timeout = purple_timeout_add (delay, ap_update_cb, data); + g_hash_table_insert (update_timeouts, data, GINT_TO_POINTER (timeout)); + g_static_mutex_unlock (&update_timeout_mutex); +void ap_update (APUpdateType type) + ap_update_cb (GINT_TO_POINTER (type)); +void ap_update_after_delay (APUpdateType type) + g_static_mutex_lock (&update_timeout_mutex); + timeout = GPOINTER_TO_INT (g_hash_table_lookup (update_timeouts, + GINT_TO_POINTER (type))); + if (timeout) purple_timeout_remove (timeout); + timeout = purple_timeout_add (AP_SCHEDULE_UPDATE_DELAY, ap_update_cb, + GINT_TO_POINTER (type)); + g_hash_table_insert (update_timeouts, GINT_TO_POINTER (type), + GINT_TO_POINTER (timeout)); + g_static_mutex_unlock (&update_timeout_mutex); +void ap_update_stop (APUpdateType type) + g_static_mutex_lock (&update_timeout_mutex); + timeout = GPOINTER_TO_INT (g_hash_table_lookup (update_timeouts, + GINT_TO_POINTER (type))); + if (timeout) purple_timeout_remove (timeout); + g_hash_table_insert (update_timeouts, GINT_TO_POINTER (type), 0); + g_static_mutex_unlock (&update_timeout_mutex); +static void ap_account_connected (PurpleConnection *gc) { + ap_debug ("general", "Account connection detected"); + ap_update_after_delay (AP_UPDATE_PROFILE); + ap_update_after_delay (AP_UPDATE_STATUS); +void ap_update_queueing () { + if (ap_is_currently_away ()) { + if (purple_prefs_get_bool( + "/plugins/gtk/autoprofile/queue_messages_when_away")) { + purple_prefs_set_string (PIDGIN_PREFS_ROOT "/conversations/im/hide_new", "away"); + purple_prefs_set_string (PIDGIN_PREFS_ROOT "/conversations/im/hide_new", "never"); +/* Called whenever current status is changed by Purple's status menu +static void ap_status_changed ( + const char *name, PurplePrefType type, gconstpointer val, gpointer data) { + ap_debug ("general", "Status change detected"); + using_idleaway = FALSE; + ap_update (AP_UPDATE_STATUS); +void ap_autoaway_enable () { + ap_debug ("idle", "Using idleaway"); + ap_update (AP_UPDATE_STATUS); +void ap_autoaway_disable () { + ap_debug ("idle", "Disabling idleaway"); + using_idleaway = FALSE; + ap_update (AP_UPDATE_STATUS); +gboolean ap_autoaway_in_use () { +static gboolean ap_update_queue (gpointer data) + PurpleAccount *account = NULL; + PurpleConnection *gc = NULL; + g_static_mutex_lock (&update_queue_mutex); + if (queued_profiles != NULL) { + account = (PurpleAccount *) queued_profiles->data; + queued_profiles = queued_profiles->next; + g_static_mutex_unlock (&update_queue_mutex); + gc = purple_account_get_connection (account); + serv_set_info (gc, purple_account_get_user_info (account)); +static void ap_update_queue_start () + update_queue_timeout = purple_timeout_add (2000, ap_update_queue, NULL); +static void ap_update_queue_finish () + purple_timeout_remove (update_queue_timeout); + update_queue_timeout = 0; +/*--------------------------------------------------------------------------* + *--------------------------------------------------------------------------*/ +static void ap_delete_legacy_prefs () { + if (purple_prefs_exists ("/plugins/gtk/autoprofile/tab_number")) { + ap_debug ("general", "Deleting legacy preferences"); + purple_prefs_remove ("/plugins/gtk/autoprofile/components"); + purple_prefs_remove ("/plugins/gtk/autoprofile/tab_number"); + purple_prefs_remove ("/plugins/gtk/autoprofile/accounts/enable_away"); + purple_prefs_remove ("/plugins/gtk/autoprofile/accounts/enable_profile"); + purple_prefs_remove ("/plugins/gtk/autoprofile/accounts"); + purple_prefs_remove ("/plugins/gtk/autoprofile/message_titles"); + purple_prefs_remove ("/plugins/gtk/autoprofile/message_texts"); + purple_prefs_remove ("/plugins/gtk/autoprofile/default_profile"); + purple_prefs_remove ("/plugins/gtk/autoprofile/default_away"); + purple_prefs_remove ("/plugins/gtk/autoprofile/current_away"); + purple_prefs_remove ("/plugins/gtk/autoprofile/added_text"); + purple_prefs_remove ("/plugins/gtk/autoprofile/delay_profile"); + purple_prefs_remove ("/plugins/gtk/autoprofile/delay_away"); + purple_prefs_rename ("/plugins/gtk/autoprofile/text_respond", + "/plugins/gtk/autoprofile/autorespond/text"); + purple_prefs_rename ("/plugins/gtk/autoprofile/text_trigger", + "/plugins/gtk/autoprofile/autorespond/trigger"); + purple_prefs_rename ("/plugins/gtk/autoprofile/delay_respond", + "/plugins/gtk/autoprofile/autorespond/delay"); + purple_prefs_rename ("/plugins/gtk/autoprofile/use_trigger", + "/plugins/gtk/autoprofile/autorespond/enable"); +static void ap_init_preferences () { + ap_debug ("general", "Initializing preference defaults if necessary"); + /* Adding the folders */ + purple_prefs_add_none ("/plugins/gtk"); + purple_prefs_add_none ("/plugins/gtk/autoprofile"); + purple_prefs_add_none ("/plugins/gtk/autoprofile/widgets"); + purple_prefs_add_none ("/plugins/gtk/autoprofile/autorespond"); + /* Behavior-settings */ + purple_prefs_add_int ("/plugins/gtk/autoprofile/delay_update", 30); + purple_prefs_add_string ("/plugins/gtk/autoprofile/show_summary", "always"); + purple_prefs_add_bool ("/plugins/gtk/autoprofile/queue_messages_when_away", + purple_prefs_add_bool ("/plugins/gtk/autoprofile/away_when_idle", + purple_prefs_get_bool ("/core/away/away_when_idle")); + /* Auto-response settings */ + purple_prefs_add_string ("/plugins/gtk/autoprofile/autorespond/auto_reply", + purple_prefs_get_string ("/core/away/auto_reply")); + purple_prefs_add_string ("/plugins/gtk/autoprofile/autorespond/text", + _("Say the magic word if you want me to talk more!")); + purple_prefs_add_string ("/plugins/gtk/autoprofile/autorespond/trigger", + purple_prefs_add_int ("/plugins/gtk/autoprofile/autorespond/delay", 2); + purple_prefs_add_bool ("/plugins/gtk/autoprofile/autorespond/enable", TRUE); + purple_prefs_add_string_list( + "/plugins/gtk/autoprofile/profile_accounts", NULL); + purple_prefs_add_string ("/plugins/gtk/autoprofile/profile", + _("Get AutoProfile for Purple at <a href=\"" + "http://autoprofile.sourceforge.net/\">" + "autoprofile.sourceforge.net</a><br><br>[Timestamp]")); +/*--------------------------------------------------------------------------* + *--------------------------------------------------------------------------*/ +static void init_plugin (PurplePlugin *plugin) + ap_debug ("general", "Initializing AutoProfile"); + ap_init_preferences (); +PURPLE_INIT_PLUGIN (autoprofile, init_plugin, info) --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/autoprofile/autoprofile.h Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,110 @@
+/*--------------------------------------------------------------------------* + * A Purple away message and profile manager that supports dynamic text * + * AutoProfile is the legal property of its developers. 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 "../common/pp_internal.h" +#define AP_SCHEDULE_UPDATE_DELAY 3000 +#define AP_GTK_MAX_MESSAGES 50 + AP_MESSAGE_TYPE_OTHER = -1, + AP_MESSAGE_TYPE_PROFILE, + AP_MESSAGE_TYPE_AVAILABLE, +/* Variable access functions */ +PurplePlugin *ap_get_plugin_handle (); +gboolean ap_is_currently_away (); +void ap_account_enable_profile (const PurpleAccount *, gboolean); +gboolean ap_account_has_profile_enabled (const PurpleAccount *); +/* Core behavior functions */ +gchar *ap_generate (const char *, gint); +gchar *ap_get_sample_status_message (PurpleAccount *account); +void ap_update (APUpdateType); +void ap_update_after_delay (APUpdateType); +void ap_update_stop (APUpdateType); +/* Queueing functions */ +void ap_update_queueing (); +/* Auto-away functions */ +void ap_autoaway_start (); +void ap_autoaway_finish (); +void ap_autoaway_touch (); +void ap_autoaway_enable (); +void ap_autoaway_disable (); +gboolean ap_autoaway_in_use (); +/* Auto-reply functions */ +void ap_autoreply_start (); +void ap_autoreply_finish (); +void ap_gtk_make_visible (); +void ap_gtk_add_message (APUpdateType, APMessageType, const gchar *); +void ap_gtk_set_progress_visible (APUpdateType, gboolean); +GList *actions (PurplePlugin *, gpointer); +void ap_actions_finish (); +PidginPluginUiInfo ui_info; +void ap_preferences_display (); +void ap_gtk_prefs_add_summary_option (GtkWidget *); +GtkWidget *get_account_page (); +#endif /* #ifndef AUTOPROFILE_H */ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/autoprofile/autoreply.c Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,324 @@
+/*--------------------------------------------------------------------------* + * A Purple away message and profile manager that supports dynamic text * + * AutoProfile is the legal property of its developers. 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 "autoprofile.h" +#include "conversation.h" +#define SECS_BEFORE_RESENDING_AUTORESPONSE 600 +#define SEX_BEFORE_RESENDING_AUTORESPONSE "Only after you're married" +#define MILLISECS_BEFORE_PROCESSING_MSG 100 +static GSList *last_auto_responses = NULL; +struct last_auto_response { +static time_t response_timeout = 0; +/*--------------------------------------------------------------------------* + * Auto-response utility functions * + *--------------------------------------------------------------------------*/ +expire_last_auto_responses(gpointer data) + struct last_auto_response *lar; + tmp = last_auto_responses; + lar = (struct last_auto_response *)cur->data; + if ((time(NULL) - lar->sent) > SECS_BEFORE_RESENDING_AUTORESPONSE) { + last_auto_responses = g_slist_remove(last_auto_responses, lar); + return FALSE; /* do not run again */ +static struct last_auto_response * +get_last_auto_response(PurpleConnection *gc, const char *name) + struct last_auto_response *lar; + /* because we're modifying or creating a lar, schedule the + * function to expire them as the pref dictates */ + purple_timeout_add((SECS_BEFORE_RESENDING_AUTORESPONSE + 5) * 1000, + expire_last_auto_responses, NULL); + tmp = last_auto_responses; + lar = (struct last_auto_response *)tmp->data; + if (gc == lar->gc && !strncmp(name, lar->name, sizeof(lar->name))) + lar = (struct last_auto_response *)g_new0(struct last_auto_response, 1); + g_snprintf(lar->name, sizeof(lar->name), "%s", name); + last_auto_responses = g_slist_append(last_auto_responses, lar); +/*--------------------------------------------------------------------------* + * Message send/receive general functionality * + *--------------------------------------------------------------------------*/ +/* Detecting sent message stuff */ +static void sent_im_msg_cb (PurpleAccount *account, const char *receiver, + PurplePresence *presence; + const gchar *auto_reply_pref; + gc = purple_account_get_connection (account); + presence = purple_account_get_presence (account); + * FIXME - If "only auto-reply when away & idle" is set, then shouldn't + * this only reset lar->sent if we're away AND idle? + purple_prefs_get_string ("/plugins/gtk/autoprofile/autorespond/auto_reply"); + if ((gc->flags & PURPLE_CONNECTION_AUTO_RESP) && + !purple_presence_is_available(presence) && + strcmp(auto_reply_pref, "never")) + struct last_auto_response *lar; + lar = get_last_auto_response(gc, receiver); + lar->sent = time(NULL); +/* Detecting received message stuff */ +struct received_im_msg { + PurpleAccount *account; +static gint process_received_im_msg (gpointer data) + struct received_im_msg *received_im; + PurpleAccount *account; + PurpleConversation *conv; + received_im = (struct received_im_msg *) data; + account = received_im->account; + sender = received_im->sender; + message = received_im->message; + gc = purple_account_get_connection (account); + /* search for conversation again in case it was created by other handlers */ + conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, + conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, sender); + * Don't autorespond if: + * - it's not supported on this connection + * - or we're not idle and the 'only auto respond if idle' pref + if (gc->flags & PURPLE_CONNECTION_AUTO_RESP) + PurplePresence *presence; + PurpleStatusType *status_type; + PurpleStatusPrimitive primitive; + const gchar *auto_reply_pref; + auto_reply_pref = purple_prefs_get_string( + "/plugins/gtk/autoprofile/autorespond/auto_reply"); + presence = purple_account_get_presence(account); + status = purple_presence_get_active_status(presence); + status_type = purple_status_get_type(status); + primitive = purple_status_type_get_primitive(status_type); + if ((primitive == PURPLE_STATUS_AVAILABLE) || + (primitive == PURPLE_STATUS_INVISIBLE) || + (primitive == PURPLE_STATUS_MOBILE) || + !strcmp(auto_reply_pref, "never") || + (!purple_presence_is_idle(presence) && + !strcmp(auto_reply_pref, "awayidle"))) + away_msg = ap_get_sample_status_message (account); + if ((away_msg != NULL) && (*away_msg != '\0')) { + struct last_auto_response *lar; + gboolean autorespond_enable; + time_t now = time(NULL); + autorespond_enable = purple_prefs_get_bool ( + "/plugins/gtk/autoprofile/autorespond/enable"); + * This used to be based on the conversation window. But um, if + * you went away, and someone sent you a message and got your + * auto-response, and then you closed the window, and then they + * sent you another one, they'd get the auto-response back too + * soon. Besides that, we need to keep track of this even if we've + * got a queue. So the rest of this block is just the auto-response, + lar = get_last_auto_response(gc, sender); + if ((now - lar->sent) >= SECS_BEFORE_RESENDING_AUTORESPONSE) { + // Send basic autoresponse + serv_send_im (gc, sender, away_msg, PURPLE_MESSAGE_AUTO_RESP); + purple_conv_im_write (PURPLE_CONV_IM(conv), NULL, away_msg, + PURPLE_MESSAGE_SEND | PURPLE_MESSAGE_AUTO_RESP, + // Send additional hint if enabled + if (autorespond_enable) { + const gchar *query = purple_prefs_get_string ( + "/plugins/gtk/autoprofile/autorespond/text"); + serv_send_im (gc, sender, query, PURPLE_MESSAGE_AUTO_RESP); + purple_conv_im_write (PURPLE_CONV_IM (conv), NULL, query, + PURPLE_MESSAGE_SEND | PURPLE_MESSAGE_AUTO_RESP, + } else if (autorespond_enable && + difftime (time(NULL), response_timeout) > + purple_prefs_get_int ("/plugins/gtk/autoprofile/autorespond/delay")) { + gchar *text = purple_markup_strip_html (message); + if (match_start (text, purple_prefs_get_string ( + "/plugins/gtk/autoprofile/autorespond/trigger")) == 1) { + serv_send_im (gc, sender, away_msg, PURPLE_MESSAGE_AUTO_RESP); + purple_conv_im_write (PURPLE_CONV_IM (conv), NULL, away_msg, + PURPLE_MESSAGE_SEND | PURPLE_MESSAGE_AUTO_RESP, + response_timeout = time (NULL); + ap_debug ("autorespond", "string matched, responding"); +static void received_im_msg_cb (PurpleAccount *account, char *sender, + char *message, PurpleConversation *conv, PurpleMessageFlags flags) + struct received_im_msg *received_im; + (struct received_im_msg *) malloc (sizeof (struct received_im_msg)); + received_im->account = account; + received_im->sender = strdup (sender); + received_im->message = strdup (message); + purple_timeout_add (MILLISECS_BEFORE_PROCESSING_MSG, process_received_im_msg, +static void auto_pref_cb ( + const char *name, PurplePrefType type, gconstpointer val, gpointer data) + if (!strcmp (purple_prefs_get_string ("/core/away/auto_reply"), "never")) + purple_notify_error (NULL, NULL, + N_("This preference is disabled"), + N_("This preference currently has no effect because AutoProfile is in " + "use. To modify this behavior, use the AutoProfile configuration " + purple_prefs_set_string ("/core/away/auto_reply", "never"); +/*--------------------------------------------------------------------------* + *--------------------------------------------------------------------------*/ +void ap_autoreply_start () + purple_prefs_set_string ("/core/away/auto_reply", "never"); + purple_signal_connect (purple_conversations_get_handle (), "sent-im-msg", + ap_get_plugin_handle (), PURPLE_CALLBACK(sent_im_msg_cb), NULL); + purple_signal_connect (purple_conversations_get_handle (), "received-im-msg", + ap_get_plugin_handle (), PURPLE_CALLBACK(received_im_msg_cb), NULL); + pref_cb = purple_prefs_connect_callback (ap_get_plugin_handle (), + "/core/away/auto_reply", auto_pref_cb, NULL); +void ap_autoreply_finish () + // Assumes signals are disconnected globally + purple_prefs_disconnect_callback (pref_cb); + purple_prefs_set_string ("/core/away/auto_reply", purple_prefs_get_string ( + "/plugins/gtk/autoprofile/autorespond/auto_reply")); + while (last_auto_responses) { + tmp = last_auto_responses->next; + g_free (last_auto_responses->data); + g_slist_free_1 (last_auto_responses); + last_auto_responses = tmp; --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/autoprofile/comp_countdownup.c Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,438 @@
+/*--------------------------------------------------------------------------* + * A Purple away message and profile manager that supports dynamic text * + * AutoProfile is the legal property of its developers. 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 "../common/pp_internal.h" +static GtkWidget *spin_secs; +static GtkWidget *spin_mins; +static GtkWidget *spin_hour; +static GtkWidget *spin_day; +static GtkWidget *spin_month; +static GtkWidget *spin_year; +/* Generate the time! */ +char *count_generate (struct widget *w) + double d_secs, d_mins, d_hours, d_days; + char *s_secs, *s_mins, *s_hours, *s_days; + ref_time = (struct tm *) malloc (sizeof (struct tm)); + ref_time->tm_sec = ap_prefs_get_int (w, "secs"); + ref_time->tm_min = ap_prefs_get_int (w, "mins"); + ref_time->tm_hour = ap_prefs_get_int (w, "hour"); + ref_time->tm_mday = ap_prefs_get_int (w, "day"); + ref_time->tm_mon = ap_prefs_get_int (w, "month") - 1; + ref_time->tm_year = ap_prefs_get_int (w, "year") - 1900; + ref_time->tm_isdst = -1; + if (ap_prefs_get_int (w, "down") == 1) + difference = difftime (mktime (ref_time), time(NULL)); + difference = difftime (time(NULL), mktime (ref_time)); + d_mins = floor (difference / 60); + d_secs = difference - (d_mins * 60); + d_hours = floor (d_mins / 60); + d_mins = d_mins - (d_hours * 60); + d_days = floor (d_hours / 24); + d_hours = d_hours - (d_days * 24); + result = (char *)malloc(sizeof (char) * AP_SIZE_MAXIMUM); + l = ap_prefs_get_int (w, "large"); + s = ap_prefs_get_int (w, "small"); + g_snprintf(result, AP_SIZE_MAXIMUM, + "%.0f days, %.0f hours, %.0f minutes, %.0f seconds", + d_days, d_hours, d_mins, d_secs); + d_hours = d_hours + (d_days * 24); + d_mins = d_mins + (d_hours * 60); + d_secs = d_secs + (d_mins * 60); + s_days = g_strdup ("day"); + s_days = g_strdup ("days"); + s_hours = g_strdup ("hour"); + s_hours = g_strdup ("hours"); + s_mins = g_strdup ("minute"); + s_mins = g_strdup ("minutes"); + s_secs = g_strdup ("second"); + s_secs = g_strdup ("seconds"); + g_snprintf (result, AP_SIZE_MAXIMUM, "%.0f %s", + g_snprintf (result, AP_SIZE_MAXIMUM, "%.0f %s, %.0f %s", + d_days, s_days, d_hours, s_hours); + g_snprintf (result, AP_SIZE_MAXIMUM, "%.0f %s, %.0f %s, %.0f %s", + d_days, s_days, d_hours, s_hours, d_mins, s_mins); + g_snprintf (result, AP_SIZE_MAXIMUM, + "%.0f %s, %.0f %s, %.0f %s, %.0f %s", + d_days, s_days, d_hours, s_hours, d_mins, s_mins, d_secs, s_secs); + g_snprintf (result, AP_SIZE_MAXIMUM, "%.0f %s", + g_snprintf (result, AP_SIZE_MAXIMUM, "%.0f %s, %.0f %s", + d_hours, s_hours, d_mins, s_mins); + g_snprintf (result, AP_SIZE_MAXIMUM, "%.0f %s, %.0f %s, %.0f %s", + d_hours, s_hours, d_mins, s_mins, d_secs, s_secs); + g_snprintf (result, AP_SIZE_MAXIMUM, "%.0f %s", + g_snprintf (result, AP_SIZE_MAXIMUM, "%.0f %s, %.0f %s", + d_mins, s_mins, d_secs, s_secs); + g_snprintf (result, AP_SIZE_MAXIMUM, "%.0f %s", +static void update_year (GtkSpinButton *spinner, struct widget *w) + int value = gtk_spin_button_get_value_as_int (spinner); + ap_prefs_set_int (w, "year", value); +static void update_month (GtkSpinButton *spinner, struct widget *w) + int value = gtk_spin_button_get_value_as_int (spinner); + ap_prefs_set_int (w, "month", value); +static void update_day (GtkSpinButton *spinner, struct widget *w) + int value = gtk_spin_button_get_value_as_int (spinner); + ap_prefs_set_int (w, "day", value); +static void update_hour (GtkSpinButton *spinner, struct widget *w) + int value = gtk_spin_button_get_value_as_int (spinner); + ap_prefs_set_int (w, "hour", value); +static void update_mins (GtkSpinButton *spinner, struct widget *w) + int value = gtk_spin_button_get_value_as_int (spinner); + ap_prefs_set_int (w, "mins", value); +static void update_secs (GtkSpinButton *spinner, struct widget *w) + int value = gtk_spin_button_get_value_as_int (spinner); + ap_prefs_set_int (w, "secs", value); +static void set_to_current_time (GtkButton *button, struct widget *w) + ref_time = ap_localtime(&the_time); + ap_prefs_set_int (w, "year", ref_time->tm_year + 1900); + ap_prefs_set_int (w, "month", ref_time->tm_mon + 1); + ap_prefs_set_int (w, "day", ref_time->tm_mday); + ap_prefs_set_int (w, "hour", ref_time->tm_hour); + ap_prefs_set_int (w, "mins", ref_time->tm_min); + ap_prefs_set_int (w, "secs", ref_time->tm_sec); + if (spin_secs != NULL) { + gtk_spin_button_set_value (GTK_SPIN_BUTTON (spin_secs), + ap_prefs_get_int (w, "secs")); + if (spin_mins != NULL) { + gtk_spin_button_set_value (GTK_SPIN_BUTTON (spin_mins), + ap_prefs_get_int (w, "mins")); + if (spin_hour != NULL) { + gtk_spin_button_set_value (GTK_SPIN_BUTTON (spin_hour), + ap_prefs_get_int (w, "hour")); + if (spin_day != NULL) { + gtk_spin_button_set_value (GTK_SPIN_BUTTON (spin_day), + ap_prefs_get_int (w, "day")); + if (spin_month != NULL) { + gtk_spin_button_set_value (GTK_SPIN_BUTTON (spin_month), + ap_prefs_get_int (w, "month")); + if (spin_year != NULL) { + gtk_spin_button_set_value (GTK_SPIN_BUTTON (spin_year), + ap_prefs_get_int (w, "year")); +GtkWidget *count_menu (struct widget *w) + GtkWidget *vbox, *hbox, *big_hbox, *frame; + GtkWidget *label, *spinner, *dropbox, *button; + big_hbox = gtk_hbox_new (FALSE, 6); + frame = pidgin_make_frame (big_hbox, _("Start/end time")); + vbox = gtk_vbox_new (FALSE, 6); + gtk_container_add (GTK_CONTAINER (frame), vbox); + hbox = gtk_hbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + label = gtk_label_new (_("Year: ")); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + gtk_box_pack_start (GTK_BOX(hbox), label, TRUE, TRUE, 0); + spinner = gtk_spin_button_new_with_range (1970, 2035, 1); + gtk_box_pack_end (GTK_BOX (hbox), spinner, FALSE, FALSE, 0); + gtk_spin_button_set_value (GTK_SPIN_BUTTON (spinner), + ap_prefs_get_int (w, "year")); + g_signal_connect (G_OBJECT (spinner), "value-changed", + G_CALLBACK (update_year), w); + hbox = gtk_hbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + label = gtk_label_new (_("Month: ")); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + gtk_box_pack_start (GTK_BOX(hbox), label, TRUE, TRUE, 0); + spinner = gtk_spin_button_new_with_range (1, 12, 1); + gtk_box_pack_end (GTK_BOX (hbox), spinner, FALSE, FALSE, 0); + gtk_spin_button_set_value (GTK_SPIN_BUTTON (spinner), + ap_prefs_get_int (w, "month")); + g_signal_connect (G_OBJECT (spinner), "value-changed", + G_CALLBACK (update_month), w); + hbox = gtk_hbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + label = gtk_label_new (_("Day: ")); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + gtk_box_pack_start (GTK_BOX(hbox), label, TRUE, TRUE, 0); + spinner = gtk_spin_button_new_with_range (1, 31, 1); + gtk_box_pack_end (GTK_BOX (hbox), spinner, FALSE, FALSE, 0); + gtk_spin_button_set_value (GTK_SPIN_BUTTON (spinner), + ap_prefs_get_int (w, "day")); + g_signal_connect (G_OBJECT (spinner), "value-changed", + G_CALLBACK (update_day), w); + hbox = gtk_hbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + label = gtk_label_new (_("Hour: ")); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + gtk_box_pack_start (GTK_BOX(hbox), label, TRUE, TRUE, 0); + spinner = gtk_spin_button_new_with_range (0, 23, 1); + gtk_box_pack_end (GTK_BOX (hbox), spinner, FALSE, FALSE, 0); + gtk_spin_button_set_value (GTK_SPIN_BUTTON (spinner), + ap_prefs_get_int (w, "hour")); + g_signal_connect (G_OBJECT (spinner), "value-changed", + G_CALLBACK (update_hour), w); + hbox = gtk_hbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + label = gtk_label_new (_("Minutes: ")); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + gtk_box_pack_start (GTK_BOX(hbox), label, TRUE, TRUE, 0); + spinner = gtk_spin_button_new_with_range (0, 59, 1); + gtk_box_pack_end (GTK_BOX (hbox), spinner, FALSE, FALSE, 0); + gtk_spin_button_set_value (GTK_SPIN_BUTTON (spinner), + ap_prefs_get_int (w, "mins")); + g_signal_connect (G_OBJECT (spinner), "value-changed", + G_CALLBACK (update_mins), w); + hbox = gtk_hbox_new (FALSE, 12); + gtk_box_pack_start (GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + label = gtk_label_new (_("Seconds: ")); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + gtk_box_pack_start (GTK_BOX(hbox), label, TRUE, TRUE, 0); + spinner = gtk_spin_button_new_with_range (0, 59, 1); + gtk_box_pack_end (GTK_BOX (hbox), spinner, FALSE, FALSE, 0); + gtk_spin_button_set_value (GTK_SPIN_BUTTON (spinner), + ap_prefs_get_int (w, "secs")); + g_signal_connect (G_OBJECT (spinner), "value-changed", + G_CALLBACK (update_secs), w); + hbox = gtk_hbox_new (FALSE, 12); + gtk_box_pack_start (GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + button = gtk_button_new_with_label ("Set to current time"); + g_signal_connect (G_OBJECT (button), "clicked", + G_CALLBACK (set_to_current_time), w); + gtk_box_pack_start (GTK_BOX(hbox), button, FALSE, FALSE, 0); + frame = pidgin_make_frame (big_hbox, _("Which way")); + vbox = gtk_vbox_new (FALSE, 6); + gtk_container_add (GTK_CONTAINER (frame), vbox); + options = g_list_append (NULL, (char *) _("Count down to stop date")); + options = g_list_append (options, GINT_TO_POINTER(1)); + options = g_list_append (options, (char *) + _("Count time since start date")); + options = g_list_append (options, GINT_TO_POINTER(0)); + dropbox = ap_prefs_dropdown_from_list (w, vbox, NULL, + PURPLE_PREF_INT, "down", options); + options = g_list_append (NULL, (char *) _("Days")); + options = g_list_append (options, GINT_TO_POINTER(3)); + options = g_list_append (options, (char *) _("Hours")); + options = g_list_append (options, GINT_TO_POINTER(2)); + options = g_list_append (options, (char *) _("Minutes")); + options = g_list_append (options, GINT_TO_POINTER(1)); + options = g_list_append (options, (char *) _("Seconds")); + options = g_list_append (options, GINT_TO_POINTER(0)); + dropbox = ap_prefs_dropdown_from_list (w, vbox, + _("Largest units displayed"), PURPLE_PREF_INT, "large", options); + dropbox = ap_prefs_dropdown_from_list (w, vbox, + _("Smallest units displayed"), PURPLE_PREF_INT, "small", options); +void count_init (struct widget *w) { + ref_time = ap_localtime(&the_time); + ap_prefs_add_int (w, "down", 1); + ap_prefs_add_int (w, "small", 0); + ap_prefs_add_int (w, "large", 3); + ap_prefs_add_int (w, "year", + ref_time->tm_year + 1900); + ap_prefs_add_int (w, "month", + ap_prefs_add_int (w, "day", + ap_prefs_add_int (w, "hour", + ap_prefs_add_int (w, "mins", + ap_prefs_add_int (w, "secs", +struct component count = + N_("Given a date, shows amount of time until it (or since it)"), --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/autoprofile/comp_executable.c Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,169 @@
+/*--------------------------------------------------------------------------* + * A Purple away message and profile manager that supports dynamic text * + * AutoProfile is the legal property of its developers. 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 "../common/pp_internal.h" +/*---------- EXECUTABLE: STDOUT from a program ----------*/ +static GtkWidget *file_selector; +static GtkWidget *file_entry; +/* Read file into string and return */ +char *executable_generate (struct widget *w) + char *text, *text_start; + max = ap_prefs_get_int (w, "max_size"); + exec = g_spawn_command_line_sync (ap_prefs_get_string (w, "command"), + &text_start, NULL, NULL, &return_error); + ap_debug ("executable", "command failed to execute"); + return g_strdup (_("[ERROR: command failed to execute]")); + if (strlen (text_start) < max) + text = text_start + strlen(text_start); + text = text_start + max; + /* Should back off only if the last item is newline */ + /* Gets rid of the extra <BR> in output */ +void executable_filename (GtkWidget *widget, gpointer user_data) { + const gchar *selected_filename; + selected_filename = gtk_file_selection_get_filename ( + GTK_FILE_SELECTION (file_selector)); + ap_prefs_set_string ((struct widget *) user_data, "command", + gtk_entry_set_text (GTK_ENTRY (file_entry), selected_filename); +/* Creates and pops up file selection dialog for fortune file */ +void executable_selection (GtkWidget *widget, struct widget *w) { + /* Create the selector */ + file_selector = gtk_file_selection_new ( + "Select the location of the program"); + cur_file = ap_prefs_get_string (w, "command"); + if (strlen (cur_file) > 1) { + gtk_file_selection_set_filename ( + GTK_FILE_SELECTION (file_selector), cur_file); + g_signal_connect (GTK_OBJECT( + GTK_FILE_SELECTION(file_selector)->ok_button), + "clicked", G_CALLBACK (executable_filename), w); + /* Destroy dialog box when the user clicks button. */ + g_signal_connect_swapped (GTK_OBJECT( + GTK_FILE_SELECTION(file_selector)->ok_button), + "clicked", G_CALLBACK (gtk_widget_destroy), (gpointer) file_selector); + g_signal_connect_swapped (GTK_OBJECT ( + GTK_FILE_SELECTION (file_selector)->cancel_button), + "clicked", G_CALLBACK (gtk_widget_destroy), (gpointer) file_selector); + gtk_widget_show (file_selector); +static gboolean executable_update (GtkWidget *widget, GdkEventFocus *evt, + ap_prefs_set_string ((struct widget *) data, "command", + gtk_entry_get_text (GTK_ENTRY (file_entry))); +GtkWidget *executable_menu (struct widget *w) + GtkWidget *ret = gtk_vbox_new (FALSE, 5); + GtkWidget *hbox, *label, *button; + label = gtk_label_new ( + _("Specify the command line you wish to execute")); + gtk_box_pack_start (GTK_BOX (ret), label, FALSE, FALSE, 0); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + hbox = gtk_hbox_new (FALSE, 5); + gtk_box_pack_start (GTK_BOX (ret), hbox, FALSE, FALSE, 0); + /* Text entry to type in program name */ + file_entry = gtk_entry_new (); + gtk_box_pack_start (GTK_BOX (hbox), file_entry, FALSE, FALSE, 0); + gtk_entry_set_text (GTK_ENTRY (file_entry), + ap_prefs_get_string (w, "command")); + g_signal_connect (G_OBJECT (file_entry), "focus-out-event", + G_CALLBACK (executable_update), w); + /* Button to bring up file select dialog */ + button = gtk_button_new_with_label ("Browse for program"); + g_signal_connect (G_OBJECT (button), "clicked", + G_CALLBACK (executable_selection), w); + gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0); + ap_prefs_labeled_spin_button (w, ret, + _("Max characters to read from output: "), "max_size", + 1, AP_SIZE_MAXIMUM, NULL); +void executable_init (struct widget *w) { + ap_prefs_add_string (w, "command", "date"); + ap_prefs_add_int (w, "max_size", 1000); +struct component executable = + N_("Reproduces standard output of running a program on the command line"), --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/autoprofile/comp_http.c Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,204 @@
+/*--------------------------------------------------------------------------* + * A Purple away message and profile manager that supports dynamic text * + * AutoProfile is the legal property of its developers. 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 "../common/pp_internal.h" +static GHashTable *refresh_timeouts = NULL; +/*---------- HTTP: HTTP requested Data ----------*/ +static void http_response (PurpleUtilFetchUrlData *reuqest_data, gpointer data, const char *c, gsize len, const gchar *error_message) + w = (struct widget *) data; + ap_prefs_set_string (w, "http_data", + _("[AutoProfile error: Invalid URL or no internet connection]")); + w = (struct widget *) data; + ap_prefs_set_string (w, "http_data", c); +static char* http_generate (struct widget *w) + const char *result, *url; + url = ap_prefs_get_string (w, "http_url"); + if (!url || url[0] == '\0') { + return g_strdup (_("[AutoProfile error: No URL specified]")); + result = ap_prefs_get_string (w, "http_data"); + if (result == NULL) return g_strdup (""); + return g_strdup (result); +static gboolean http_refresh_update (gpointer user_data) + w = (struct widget *) user_data; + http_url = g_strdup (ap_prefs_get_string (w, "http_url")); + if( http_url && (http_url[0] != '\0') ) { + purple_util_fetch_url(http_url, TRUE, NULL, FALSE, http_response, w); + ap_prefs_set_string (w, "http_data", ""); +static void http_load (struct widget *w) + gpointer http_refresh_timeout; + if (refresh_timeouts == NULL) { + refresh_timeouts = g_hash_table_new (NULL, NULL); + http_refresh_update (w); + http_refresh_timeout = GINT_TO_POINTER (g_timeout_add ( + ap_prefs_get_int (w, "http_refresh_mins") * 60 * 1000, + http_refresh_update, w)); + g_hash_table_insert (refresh_timeouts, w, http_refresh_timeout); +static void http_unload (struct widget *w) + gpointer http_refresh_timeout; + http_refresh_timeout = g_hash_table_lookup (refresh_timeouts, w); + g_source_remove (GPOINTER_TO_INT (http_refresh_timeout)); + g_hash_table_remove (refresh_timeouts, w); +static void http_init (struct widget *w) + ap_prefs_add_string (w, "http_url", ""); + ap_prefs_add_string (w, "http_data", ""); + ap_prefs_add_int (w, "http_refresh_mins", 1); +static gboolean http_url_update (GtkWidget *widget, GdkEventFocus *evt, + struct widget *w = (struct widget *) data; + ap_prefs_set_string (w, "http_url", + gtk_entry_get_text (GTK_ENTRY (widget))); +static gboolean http_refresh_mins_update (GtkWidget *widget, gpointer data) + minutes = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (widget)); + w = (struct widget *) data; + ap_prefs_set_int (w, "http_refresh_mins", minutes); + // Kill the current timer and run a new one + timeout = g_hash_table_lookup (refresh_timeouts, w); + g_source_remove (GPOINTER_TO_INT(timeout)); + timeout = GINT_TO_POINTER (g_timeout_add (minutes * 60 * 1000, + http_refresh_update, w)); + g_hash_table_replace (refresh_timeouts, w, timeout); +static void http_data_update (GtkWidget *w, gpointer data) { + http_refresh_update (data); +static GtkWidget *http_menu (struct widget *w) + GtkWidget *ret = gtk_vbox_new (FALSE, 5); + GtkWidget *label, *hbox, *button, *spinner; + GtkWidget *http_url_entry; + label = gtk_label_new (_("Select URL with source content")); + gtk_box_pack_start (GTK_BOX (ret), label, FALSE, FALSE, 0); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + hbox = gtk_hbox_new (FALSE, 5); + gtk_box_pack_start (GTK_BOX (ret), hbox, FALSE, FALSE, 0); + http_url_entry = gtk_entry_new (); + gtk_box_pack_start (GTK_BOX (hbox), http_url_entry, TRUE, TRUE, 0); + gtk_entry_set_text (GTK_ENTRY (http_url_entry), + ap_prefs_get_string (w, "http_url")); + g_signal_connect (G_OBJECT (http_url_entry), "focus-out-event", + G_CALLBACK (http_url_update), w); + button = gtk_button_new_with_label (_("Fetch page now!")); + g_signal_connect (G_OBJECT (button), "clicked", + G_CALLBACK (http_data_update), w); + gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0); + hbox = gtk_hbox_new (FALSE, 5); + gtk_box_pack_start (GTK_BOX (ret), hbox, FALSE, FALSE, 0); + label = gtk_label_new (_("Delay")); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + spinner = gtk_spin_button_new_with_range (1, 60, 1); + gtk_box_pack_start (GTK_BOX (hbox), spinner, FALSE, FALSE, 0); + gtk_spin_button_set_value (GTK_SPIN_BUTTON (spinner), + ap_prefs_get_int (w, "http_refresh_mins")); + g_signal_connect (G_OBJECT (spinner), "value-changed", + G_CALLBACK (http_refresh_mins_update), w); + label = gtk_label_new (_("minutes between page fetches")); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + N_("Data fetched from an internet URL using HTTP"), --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/autoprofile/comp_logstats.c Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,1042 @@
+/*--------------------------------------------------------------------------* + * A Purple away message and profile manager that supports dynamic text * + * AutoProfile is the legal property of its developers. 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 "autoprofile.h" +#include "conversation.h" +#include "comp_logstats.h" +struct conversation_time { +/* Represents data about a particular 24 hour period in the logs */ + int month; // The month + int received_msgs; // # msgs received + int received_words; // # words received + int sent_msgs; // # msgs sent + int sent_words; // # words sent + GSList *conversation_times; // List of conversation_time pointers +/* List of struct log_dates + This is SORTED by most recent first */ +static GSList *dates = NULL; +/* Hashtable of log_dates */ +static GHashTable *dates_table = NULL; +/* Is the current line part of a message sent or received? */ +static gboolean receiving = FALSE; +static char *cur_receiver = NULL; +static char *cur_sender = NULL; +/* Implements GCompareFunc */ +static gint conversation_time_compare (gconstpointer x, gconstpointer y) { + const struct conversation_time *a = x; + const struct conversation_time *b = y; + if (difftime (*(a->start_time), *(b->start_time)) == 0.0) { + if (!strcmp (a->name, b->name)) +/* Implements GCompareFunc */ +static gint log_date_compare (gconstpointer x, gconstpointer y) + const struct log_date *a = y; + const struct log_date *b = x; + if (a->year == b->year) { + if (a->month == b->month) { + return a->day - b->day; + return a->month - b->month; + return a->year - b->year; +/* Implements GHashFunc */ +static guint log_date_hash (gconstpointer key) + const struct log_date *d = key; + return ((d->year * 365) + (d->month * 12) + (d->day)); +/* Implements GEqualFunc */ +static gboolean log_date_equal (gconstpointer x, gconstpointer y) + const struct log_date *a = y; + const struct log_date *b = x; + if (a->year == b->year && + a->month == b->month && +/* Returns the struct log_date associated with a particular date. + Will MODIFY list of dates and insert sorted if not yet created */ +static struct log_date *get_date (int year, int month, int day) + struct log_date *cur_date; + cur_date = (struct log_date *)malloc(sizeof(struct log_date)); + cur_date->month = month; + if ((node = g_hash_table_lookup (dates_table, cur_date))) { + return (struct log_date *)node; + g_hash_table_insert (dates_table, cur_date, cur_date); + cur_date->received_msgs = 0; + cur_date->received_words = 0; + cur_date->sent_msgs = 0; + cur_date->sent_words = 0; + cur_date->conversation_times = NULL; +/* Like get_date, except specific to the current date */ +static struct log_date *get_today () + cur_time = localtime (&the_time); + return get_date (cur_time->tm_year, cur_time->tm_mon, cur_time->tm_mday); +static int string_word_count (const char *line) + /* If state is 1, currently processing a word */ +/* Figure out if a person is yourself or someone else */ +static gboolean is_self (PurpleAccount *a, const char *name) { + GList *accounts, *aliases, *aliases_start; + PurpleAccount *account; + const char *normalized_alias; + if (cur_sender && !strcmp (cur_sender, name)) { + if (cur_receiver && !strcmp (cur_receiver, name)) { + normalized = strdup (purple_normalize (a, name)); + accounts = purple_accounts_get_all (); + aliases_start = aliases = purple_prefs_get_string_list ( + "/plugins/gtk/autoprofile/components/logstat/aliases"); + normalized_alias = purple_normalize (a, (char *)aliases->data); + if (!strcmp (normalized, normalized_alias)) { + free_string_list (aliases_start); + cur_sender = strdup (name); + aliases = aliases->next; + free_string_list (aliases_start); + account = (PurpleAccount *)accounts->data; + if (!strcmp (normalized, purple_account_get_username (account))) { + cur_sender = strdup (name); + accounts = accounts->next; + cur_receiver = strdup (name); +/* Parses a line of a conversation */ +static void parse_line (PurpleLog *cur_log, char *l, struct log_date *d) + char *cur_line, *cur_line_start; + if (strlen (line) > 14 && *line == ' ') + if (strlen (line) > 13 && + isdigit (*(line + 1)) && + isdigit (*(line + 2)) && + isdigit (*(line + 4)) && + isdigit (*(line + 5)) && + isdigit (*(line + 7)) && + isdigit (*(line + 8)) && + isspace (*(line + 10))) { + cur_line_start = cur_line = line + 11; + if (*cur_line == ':') { + receiving = !is_self (cur_log->account, name); + d->received_words += string_word_count (message); + d->sent_words += string_word_count (message); + d->received_words += string_word_count (line); + d->sent_words += string_word_count (line); +/* Parses a conversation if hasn't been handled yet */ +static void parse_log (PurpleLog *cur_log) + struct log_date *the_date; + struct conversation_time *conv_time; + PurpleLogReadFlags flags; + char *content, *cur_content, *cur_content_start, *temp; + the_time = localtime (&(cur_log->time)); + the_date = get_date (the_time->tm_year, the_time->tm_mon, the_time->tm_mday); + /* Check for old log and if no conflicts, add to list */ + conv_time = (struct conversation_time *)malloc ( + sizeof (struct conversation_time)); + conv_time->start_time = (time_t *)malloc (sizeof(time_t)); + *(conv_time->start_time) = cur_log->time; + conv_time->name = strdup (cur_log->name); + if (g_slist_find_custom (the_date->conversation_times, conv_time, + conversation_time_compare)) { + /* We already processed this! Halt! */ + free (conv_time->start_time); + free (conv_time->name); + the_date->conversation_times = g_slist_prepend (the_date->conversation_times, + /* Start rolling the counters! */ + temp = purple_log_read (cur_log, &flags); + if (!strcmp ("html", cur_log->logger->id)) { + content = purple_markup_strip_html (temp); + cur_content_start = cur_content = content; + /* Splits the conversation into lines (each line may not necessarily + be a seperate message */ + if (*cur_content == '\n') { + parse_line (cur_log, cur_content_start, the_date); + cur_content_start = cur_content + 1; + parse_line (cur_log, cur_content_start, the_date); +/* Get names of users in logs */ +static GList *logstats_get_names (PurpleLogType type, PurpleAccount *account) + if (type == PURPLE_LOG_CHAT) + me = g_strdup_printf ("%s.chat", purple_normalize(account, + purple_account_get_username(account))); + me = g_strdup (purple_normalize(account, + purple_account_get_username(account))); + /* Get the old logger names */ + path = g_build_filename(purple_user_dir(), "logs", NULL); + if (!(dir = g_dir_open(path, 0, NULL))) { + while ((filename = g_dir_read_name (dir))) { + if (purple_str_has_suffix (filename, ".log")) { + tmp = strdup (filename); + *(tmp + strlen (filename) - 4) = '\0'; + if (!string_list_find (ret, tmp)) + ret = g_list_prepend (ret, strdup (tmp)); + /* Get the account-specific names */ + prpl = PURPLE_PLUGIN_PROTOCOL_INFO + (purple_find_prpl (purple_account_get_protocol_id(account)))->list_icon( + path = g_build_filename(purple_user_dir(), "logs", prpl, me, NULL); + if (!(dir = g_dir_open(path, 0, NULL))) { + while ((filename = g_dir_read_name (dir))) { + if (!string_list_find (ret, filename)) + ret = g_list_prepend (ret, strdup (filename)); +/* On load, reads in all logs and initializes stats database */ +static void logstats_read_logs () + GList *accounts, *logs, *logs_start, *names, *names_start; + accounts = purple_accounts_get_all(); + ap_debug ("logstats", "parsing log files"); + names_start = names = logstats_get_names (PURPLE_LOG_IM, + (PurpleAccount *)accounts->data); + logs_start = purple_log_get_logs (PURPLE_LOG_IM, (char *)names->data, + (PurpleAccount *)accounts->data); + cur_log = (PurpleLog *)logs->data; + purple_log_free (cur_log); + g_list_free (logs_start); + free_string_list (names_start); + accounts = accounts->next; + ap_debug ("logstats", "finished parsing log files"); +static void add_element (gpointer key, gpointer value, gpointer data) + dates = g_slist_insert_sorted (dates, value, log_date_compare); +/* Updates GList against hashtable */ +static void logstats_update_dates () + g_hash_table_foreach (dates_table, add_element, NULL); +/*--------------------- Total calculations -------------------*/ +static int get_total (const char *field) + d = (struct log_date *)cur_day->data; + if (!strcmp (field, "received_msgs")) { + count += d->received_msgs; + } else if (!strcmp (field, "received_words")) { + count += d->received_words; + } else if (!strcmp (field, "sent_msgs")) { + } else if (!strcmp (field, "sent_words")) { + count += d->sent_words; + } else if (!strcmp (field, "num_convos")) { + count += g_slist_length (d->conversation_times); + cur_day = cur_day->next; +static int get_recent_total (const char *field, int hours) + d = (struct log_date *)cur_day->data; + cur_day_time = purple_time_build (d->year + 1900, d->month + 1, d->day, + if (difftime (time (NULL), cur_day_time) > (double) hours * 60.0 * 60.0) + if (!strcmp (field, "received_msgs")) { + count += d->received_msgs; + } else if (!strcmp (field, "sent_msgs")) { + } else if (!strcmp (field, "num_convos")) { + count += g_slist_length (d->conversation_times); + cur_day = cur_day->next; +static int num_days_since_start () + first_day = g_slist_last (dates); + d = (struct log_date *)first_day->data; + difference = difftime ( + time (NULL), purple_time_build (d->year + 1900, d->month + 1, d->day, + return (int) difference / (60.0 * 60.0 * 24.0); +static struct log_date *get_max_date (const char *field) + struct log_date *max_date, *cur_date; + int max_so_far, cur_max; + cur_date = (struct log_date *)cur_day->data; + if (!strcmp (field, "conversations")) { + cur_max = g_slist_length (cur_date->conversation_times); + } else if (!strcmp (field, "received")) { + cur_max = cur_date->received_msgs; + } else if (!strcmp (field, "sent")) { + cur_max = cur_date->sent_msgs; + } else if (!strcmp (field, "total")) { + cur_max = cur_date->sent_msgs + cur_date->received_msgs; + if (cur_max >= max_so_far) { + cur_day = cur_day->next; +static char *date_string (const char *field) + last_day = g_slist_last (dates); + if (!strcmp (field, "first")) { + d = (struct log_date *) last_day->data; + d = get_max_date (field); + output = (char *)malloc (sizeof(char) * AP_SIZE_MAXIMUM); + t_struct = (struct tm *)malloc(sizeof(struct tm)); + t_struct->tm_year = d->year; + t_struct->tm_mon = d->month; + t_struct->tm_mday = d->day; + t_struct = localtime (&t); + strftime (output, AP_SIZE_MAXIMUM - 1, "%a %b %d, %Y", t_struct); +static int get_max (const char *field) + struct log_date *max_date = get_max_date (field); + if (!strcmp (field, "conversations")) { + return g_slist_length (max_date->conversation_times); + } else if (!strcmp (field, "received")) { + return max_date->received_msgs; + } else if (!strcmp (field, "sent")) { + return max_date->sent_msgs; + } else if (!strcmp (field, "total")) { + return max_date->sent_msgs + max_date->received_msgs; + ap_debug ("logstats", "get-max: invalid paramater"); +/*--------------------- Signal handlers ----------------------*/ +static void logstats_received_im (PurpleAccount *account, char *sender, + char *message, int flags) + struct log_date *the_date; + the_date = get_today (); + the_date->received_msgs++; + the_date->received_words += string_word_count (message); +static void logstats_sent_im (PurpleAccount *account, const char *receiver, + struct log_date *the_date; + the_date = get_today (); + the_date->sent_words += string_word_count (message); +static void logstats_conv_created (PurpleConversation *conv) + struct log_date *the_date; + struct conversation_time *the_time; + if (conv->type == PURPLE_CONV_TYPE_IM) { + the_time = malloc (sizeof(struct conversation_time)); + the_time->name = strdup (conv->name); + the_time->start_time = malloc (sizeof(time_t)); + time (the_time->start_time); + the_date = get_today (); + the_date->conversation_times = g_slist_prepend ( + the_date->conversation_times, the_time); + logstats_update_dates (); +/*--------------------------- Main functions -------------------------*/ +void logstats_load (struct widget *w) + if (!purple_prefs_get_bool ( + "/plugins/gtk/autoprofile/components/logstat/enabled")) { + /* Initialize database */ + dates_table = g_hash_table_new (log_date_hash, log_date_equal); + logstats_update_dates (); + msg = (char *)malloc (sizeof(char) * AP_SIZE_MAXIMUM); + count = get_total ("received_msgs"); + g_snprintf (msg, AP_SIZE_MAXIMUM, "received msg total is %d", count); + ap_debug ("logstats", msg); + count = get_total ("sent_msgs"); + g_snprintf (msg, AP_SIZE_MAXIMUM, "sent msg total is %d", count); + ap_debug ("logstats", msg); + count = get_total ("received_words"); + g_snprintf (msg, AP_SIZE_MAXIMUM, "received word total is %d", count); + ap_debug ("logstats", msg); + count = get_total ("sent_words"); + g_snprintf (msg, AP_SIZE_MAXIMUM, "sent word total is %d", count); + ap_debug ("logstats", msg); + count = get_total ("num_convos"); + g_snprintf (msg, AP_SIZE_MAXIMUM, "num conversations is %d", count); + ap_debug ("logstats", msg); + count = g_slist_length (dates); + g_snprintf (msg, AP_SIZE_MAXIMUM, "num days with conversations is %d", count); + ap_debug ("logstats", msg); + purple_signal_connect (purple_conversations_get_handle (), + "received-im-msg", ap_get_plugin_handle (), + PURPLE_CALLBACK (logstats_received_im), NULL); + purple_signal_connect (purple_conversations_get_handle (), + "sent-im-msg", ap_get_plugin_handle (), + PURPLE_CALLBACK (logstats_sent_im), NULL); + purple_signal_connect (purple_conversations_get_handle (), + "conversation-created", ap_get_plugin_handle (), + PURPLE_CALLBACK (logstats_conv_created), NULL); +void logstats_unload (struct widget *w) + struct log_date *cur_date; + struct conversation_time *cur_time; + if (!purple_prefs_get_bool ( + "/plugins/gtk/autoprofile/components/logstat/enabled")) { + /* Disconnect signals */ + purple_signal_disconnect (purple_conversations_get_handle (), + "received-im-msg", ap_get_plugin_handle (), + PURPLE_CALLBACK (logstats_received_im)); + purple_signal_disconnect (purple_conversations_get_handle (), + "sent-im-msg", ap_get_plugin_handle (), + PURPLE_CALLBACK (logstats_sent_im)); + purple_signal_disconnect (purple_conversations_get_handle (), + "conversation-created", ap_get_plugin_handle (), + PURPLE_CALLBACK (logstats_conv_created)); + logstats_update_dates (); + /* Free all the memory */ + cur_date = (struct log_date *)dates->data; + while (cur_date->conversation_times) { + temp = cur_date->conversation_times; + cur_time = (struct conversation_time *)temp->data; + cur_date->conversation_times = temp->next; + free (cur_time->start_time); + g_hash_table_destroy (dates_table); +/* Generate the output */ +static char *logstats_generate (struct widget *w) + char *buf, *output, *date; + if (!purple_prefs_get_bool ( + "/plugins/gtk/autoprofile/components/logstat/enabled")) { + format = purple_prefs_get_string ( + "/plugins/gtk/autoprofile/components/logstat/format"); + output = (char *)malloc (sizeof(char)*AP_SIZE_MAXIMUM); + buf = (char *)malloc (sizeof(char)*AP_SIZE_MAXIMUM); + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%c", output, *format); + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, get_total ("received_msgs")); + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, get_total ("received_words")); + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, get_total ("sent_msgs")); + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, get_total ("sent_words")); + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, + get_total ("sent_msgs") + get_total ("received_msgs")); + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, + get_total ("sent_words") + get_total ("received_words")); + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, + num_days_since_start ()); + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, g_slist_length (dates)); + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, get_total ("num_convos")); + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%.2f", output, + (double) get_total ("num_convos") / (double) g_slist_length (dates)); + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, get_max ("conversations")); + date = date_string ("conversations"); + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%s", output, date); + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, get_max ("sent")); + date = date_string ("sent"); + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%s", output, date); + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, get_max ("received")); + date = date_string ("received"); + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%s", output, date); + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, get_max ("total")); + date = date_string ("total"); + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%s", output, date); + date = date_string ("first"); + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%s", output, date); + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%.2f", output, + (double) get_total ("received_words") / (double) get_total ("received_msgs")); + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%.2f", output, + (double) get_total ("sent_words") / (double) get_total ("sent_msgs")); + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%.2f", output, + (double) (get_total ("received_words") + get_total ("sent_words")) / (double) (get_total("received_msgs") + get_total ("sent_msgs"))); + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%.2f", output, + (double) get_total ("received_msgs") / (double) get_total ("num_convos")); + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%.2f", output, + (double) get_total ("sent_msgs") / (double) get_total ("num_convos")); + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%.2f", output, + (double) (get_total ("received_msgs") + get_total ("sent_msgs")) / (double) get_total("num_convos")); + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%.2f", output, + (double) get_total ("received_words") / (double) g_slist_length (dates)); + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%.2f", output, + (double) get_total ("sent_words") / (double) g_slist_length (dates)); + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%.2f", output, + ((double) get_total ("received_words") + (double) get_total ("sent_words")) / (double) g_slist_length (dates)); + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%.2f", output, + (double) get_total ("received_msgs") / (double) g_slist_length (dates)); + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%.2f", output, + (double) get_total ("sent_msgs") / (double) g_slist_length (dates)); + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%.2f", output, + (double) (get_total ("received_msgs") + get_total ("sent_msgs")) / (double) g_slist_length (dates)); + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%.1f", output, + 100.0 * (double) g_slist_length (dates) / (double) num_days_since_start ()); + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, + ((struct log_date *) dates->data)->received_msgs); + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, + ((struct log_date *) dates->data)->sent_msgs); + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, + g_slist_length (((struct log_date *) dates->data)->conversation_times)); + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, + ((struct log_date *) dates->data)->sent_msgs + ((struct log_date *) dates->data)->received_msgs); + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, get_recent_total ("received_msgs", 24 * 7)); + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, get_recent_total ("sent_msgs", 24 * 7)); + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, get_recent_total ("num_convos", 24 * 7)); + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, + get_recent_total ("received_msgs", 24 * 7) + get_recent_total ("received_msgs", 24 * 7)); + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%c", output, *format); + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%c", output, *format); +/* Initialize preferences */ +static void logstats_init (struct widget *w) + purple_prefs_add_none ("/plugins/gtk/autoprofile/components/logstat"); + purple_prefs_add_bool ( + "/plugins/gtk/autoprofile/components/logstat/enabled", FALSE); + purple_prefs_add_string ( + "/plugins/gtk/autoprofile/components/logstat/format", ""); + purple_prefs_add_string_list ( + "/plugins/gtk/autoprofile/components/logstat/aliases", NULL); +/* The heart of the component */ +static char *identifiers [7] = { +struct component logstats = + N_("Purple log statistics"), + N_("Display various statistics about your message and system logs"), --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/autoprofile/comp_logstats.h Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,28 @@
+/*--------------------------------------------------------------------------* + * A Purple away message and profile manager that supports dynamic text * + * AutoProfile is the legal property of its developers. 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 * + *--------------------------------------------------------------------------*/ +void logstats_load (struct widget *w); +void logstats_unload (struct widget *w); +GtkWidget *logstats_prefs (struct widget *w); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/autoprofile/comp_logstats_gtk.c Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,355 @@
+/*--------------------------------------------------------------------------* + * A Purple away message and profile manager that supports dynamic text * + * AutoProfile is the legal property of its developers. 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 "autoprofile.h" +#include "comp_logstats.h" +GtkWidget *checkbox = NULL; +GtkListStore *alias_list = NULL; +GtkWidget *alias_view = NULL; +/* General callbacks from main preferences */ +static void logstats_response_cb (GtkDialog *dialog, gint id, + purple_prefs_set_bool ( + "/plugins/gtk/autoprofile/components/logstat/enabled", TRUE); + gtk_widget_set_sensitive (widget, TRUE); + gtk_widget_destroy (GTK_WIDGET(dialog)); +static void toggle_enable (GtkButton *button, gpointer data) + GtkWidget *popup, *vbox, *label; + if (purple_prefs_get_bool ( + "/plugins/gtk/autoprofile/components/logstat/enabled")) { + logstats_unload (NULL); + purple_prefs_set_bool ( + "/plugins/gtk/autoprofile/components/logstat/enabled", FALSE); + gtk_widget_set_sensitive (vbox, FALSE); + popup = gtk_dialog_new_with_buttons ( + "Enable stats for logs", NULL, 0, + GTK_STOCK_OK, 42, NULL); + g_signal_connect (G_OBJECT(popup), "response", + G_CALLBACK(logstats_response_cb), vbox); + label = gtk_label_new(NULL); + gtk_label_set_markup(GTK_LABEL(label), + "\nEnabling this component will have some minor side effects. Doing so " + "will cause Purple to take slightly longer to start up because it must " + "parse a large amount of data to gather statistics. On average, this " + "can take slightly over a second for every 100,000 messages in your " + "logs.\n\nThe time from when you press the OK button to the time " + "when this dialog vanishes is a good approximation of how much extra " + "time will elapse before the login screen is shown.\n" + gtk_label_set_line_wrap(GTK_LABEL(label), TRUE); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(popup)->vbox), label, + gtk_widget_show_all (popup); +static gboolean logstat_format (GtkWidget *widget, GdkEventFocus *event, + purple_prefs_set_string ( + "/plugins/gtk/autoprofile/components/logstat/format", + gtk_entry_get_text (GTK_ENTRY (widget))); +static void new_alias (gpointer data, PurpleRequestFields *fields) + alias = purple_request_fields_get_string (fields, "alias"); + aliases = purple_prefs_get_string_list ( + "/plugins/gtk/autoprofile/components/logstat/aliases"); + aliases = g_list_append (aliases, strdup (alias)); + purple_prefs_set_string_list ( + "/plugins/gtk/autoprofile/components/logstat/aliases", aliases); + free_string_list (aliases); + gtk_list_store_insert (alias_list, &iter, 0); + gtk_list_store_set (alias_list, &iter, +static void alias_add (GtkButton *button, gpointer data) + PurpleRequestFields *fields; + PurpleRequestFieldGroup *group; + PurpleRequestField *field; + fields = purple_request_fields_new(); + group = purple_request_field_group_new(NULL); + purple_request_fields_add_group(fields, group); + field = purple_request_field_string_new("alias", _("Alias"), + purple_request_field_set_required(field, TRUE); + purple_request_field_set_type_hint(field, "alias"); + purple_request_field_group_add_field(group, field); + purple_request_fields(purple_get_blist(), _("Add Alias"), + _("Type in the alias that you use"), + _("OK"), G_CALLBACK(new_alias), + NULL, NULL, NULL, NULL); +static void alias_delete (GtkButton *button, gpointer data) + GtkTreeSelection *selection; + GList *aliases, *aliases_start, *new_aliases; + selection = gtk_tree_view_get_selection ( + GTK_TREE_VIEW (alias_view)); + if (!gtk_tree_selection_get_selected (selection, NULL, &iter)) + gtk_tree_model_get (GTK_TREE_MODEL (alias_list), &iter, + gtk_list_store_remove (alias_list, &iter); + aliases = purple_prefs_get_string_list ( + "/plugins/gtk/autoprofile/components/logstat/aliases"); + aliases_start = aliases; + if (strcmp ((char *)aliases->data, alias)) { + new_aliases = g_list_append (new_aliases, aliases->data); + aliases = aliases->next; + purple_prefs_set_string_list ( + "/plugins/gtk/autoprofile/components/logstat/aliases", new_aliases); + free_string_list (aliases_start); + g_list_free (new_aliases); +static void alias_what (GtkButton *button, gpointer data) + purple_notify_formatted (NULL, _("Aliases"), _("What this list is for"), NULL, + _("Logs in Purple are stored verbatim with what you see on the screen. " + "The names of the people in the conversation (both yourself and your " + "buddy) are shown with their given aliases as opposed to actual screen " + "names. If you have given yourself an alias in a conversation, list " + "it using this dialog. If you do not, messages written by you will " + "be incorrectly identified as received instead of sent.<br><br>Correct " + "capitalization and whitespace are not required for detection to " + "work.<br><br>You must disable/re-enable log stats to refresh the " + "database after an alias change."), +GtkWidget *logstats_prefs (struct widget *w) + GtkWidget *ret, *vbox, *hbox; + GtkWidget *label, *button, *entry, *sw; + GtkCellRenderer *renderer; + GtkTreeSelection *selection; + GtkTreeViewColumn *col; + GList *aliases, *aliases_start; + ret = gtk_vbox_new (FALSE, 6); + /* Checkbox for enabling/disabling */ + checkbox = gtk_check_button_new_with_mnemonic ( + "Enable statistics for logs"); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(checkbox), + purple_prefs_get_bool ( + "/plugins/gtk/autoprofile/components/logstat/enabled")); + gtk_box_pack_start (GTK_BOX(ret), checkbox, FALSE, FALSE, 0); + vbox = gtk_vbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX(ret), vbox, TRUE, TRUE, 0); + label = gtk_label_new (NULL); + gtk_label_set_markup (GTK_LABEL(label), "<b>Format string for output</b>"); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + gtk_box_pack_start (GTK_BOX(vbox), label, FALSE, FALSE, 0); + entry = gtk_entry_new (); + gtk_box_pack_start (GTK_BOX(vbox), entry, FALSE, FALSE, 0); + gtk_entry_set_max_length (GTK_ENTRY(entry), 1000); + gtk_entry_set_text (GTK_ENTRY(entry), purple_prefs_get_string ( + "/plugins/gtk/autoprofile/components/logstat/format")); + g_signal_connect (G_OBJECT (entry), "focus-out-event", + G_CALLBACK (logstat_format), NULL); + label = gtk_label_new (_( + "%R\tTotal messages received\n" + "%r\tTotal words received\n" + "%S\tTotal messages sent\n" + "%s\tTotal words sent\n" + "%T\tTotal messages sent/received\n" + "%t\tTotal words sent/received\n" + "%D\tNumber of days since first logged conversation\n" + "%d\tNumber of days with logged conversations\n" + "%N\tNumber of logged conversations\n" + "%n\tAverage number of conversations per day with logs\n" + "%i\tMost conversations in a single day\n" + "%I\tDate with most conversations\n" + "%j\tMost messages sent in a single day\n" + "%J\tDate with most messages sent\n" + "%k\tMost messages received in a single day\n" + "%K\tDate with most messages received\n" + "%l\tMost total messages sent/received in a single day\n" + "%L\tDate with most total messages sent/received\n" + "%f\tDate of first logged conversation\n" + "%u\tAverage words per message received\n" + "%v\tAverage words per message sent\n" + "%w\tAverage words per message sent/received\n" + "%U\tAverage messages received per conversation\n" + "%V\tAverage messages sent per conversation\n" + "%W\tAverage messages sent/received per conversation\n" + "%x\tAverage words received per day with logs\n" + "%y\tAverage words sent per day with logs\n" + "%z\tAverage words sent/received per day with logs\n" + "%X\tAverage messages received per day with logs\n" + "%Y\tAverage messages sent per day with logs\n" + "%Z\tAverage messages sent/received per day with logs\n" + "%p\tPercentage of days with logs\n" + "%a\tNumber of messages received today\n" + "%b\tNumber of messages sent today\n" + "%c\tNumber of conversations started today\n" + "%e\tNumber of messages sent/received today\n" + "%A\tNumber of messages received in last week\n" + "%B\tNumber of messages sent in last week\n" + "%C\tNumber of conversations started in last week\n" + "%E\tNumber of messages sent/received in last week\n" + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + sw = gtk_scrolled_window_new (NULL,NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), + GTK_POLICY_NEVER, GTK_POLICY_ALWAYS); + gtk_box_pack_start (GTK_BOX (vbox), sw, TRUE, TRUE , 0); + gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW(sw), label); + label = gtk_label_new (NULL); + gtk_label_set_markup (GTK_LABEL(label), "<b>Personal aliases</b>"); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + gtk_box_pack_start (GTK_BOX(vbox), label, FALSE, FALSE, 0); + label = gtk_label_new (NULL); + gtk_label_set_markup (GTK_LABEL(label), + "You need this if you have an alias for your own screen name,\n" + "else IM's you sent will be incorrectly counted as received"); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + gtk_box_pack_start (GTK_BOX(vbox), label, FALSE, FALSE, 0); + hbox = gtk_hbox_new (FALSE, 3); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + button = gtk_button_new_with_label (_("Add alias")); + g_signal_connect (G_OBJECT(button), "clicked", + G_CALLBACK (alias_add), NULL); + gtk_box_pack_start (GTK_BOX(hbox), button, TRUE, TRUE, 0); + button = gtk_button_new_with_label (_("Delete alias")); + g_signal_connect (G_OBJECT(button), "clicked", + G_CALLBACK (alias_delete), NULL); + gtk_box_pack_start (GTK_BOX(hbox), button, TRUE, TRUE, 0); + button = gtk_button_new_with_label (_("?")); + g_signal_connect (G_OBJECT(button), "clicked", + G_CALLBACK (alias_what), NULL); + gtk_box_pack_start (GTK_BOX(hbox), button, FALSE, FALSE, 0); + sw = gtk_scrolled_window_new (0, 0); + gtk_box_pack_start (GTK_BOX(vbox), sw, FALSE, FALSE, 0); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(sw), + GTK_POLICY_NEVER, GTK_POLICY_NEVER); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(sw), + alias_list = gtk_list_store_new (1, G_TYPE_STRING); + alias_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (alias_list)); + gtk_container_add (GTK_CONTAINER(sw), alias_view); + renderer = gtk_cell_renderer_text_new (); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(alias_view), FALSE); + selection = gtk_tree_view_get_selection ( + GTK_TREE_VIEW (alias_view)); + gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE); + col = gtk_tree_view_column_new_with_attributes ( + _("Alias"), renderer, "text", 0, NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW(alias_view), col); + aliases = purple_prefs_get_string_list ( + "/plugins/gtk/autoprofile/components/logstat/aliases"); + aliases_start = aliases; + gtk_list_store_append (alias_list, &iter); + gtk_list_store_set (alias_list, &iter, + 0, (char *)aliases->data, -1); + aliases = aliases->next; + free_string_list (aliases_start); + /* Finish up the checkbox stuff */ + g_signal_connect (G_OBJECT(checkbox), "clicked", + G_CALLBACK(toggle_enable), vbox); + if (!purple_prefs_get_bool ( + "/plugins/gtk/autoprofile/components/logstat/enabled")) { + gtk_widget_set_sensitive (vbox, FALSE); + gtk_widget_set_sensitive (vbox, TRUE); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/autoprofile/comp_quotation.c Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,602 @@
+/*----------------------------------------------------------------------------* + * A Purple away message and profile manager that supports dynamic text * + * AutoProfile is the legal property of its developers. 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 "autoprofile.h" + QUOTATION_LIST_STORE = 1, + QUOTATION_FILE_SELECTOR, +/*--------------------------------------------------------------------------* + * Menu related things * + *--------------------------------------------------------------------------*/ +static void append_quote (struct widget *w, GtkListStore *ls, gchar *quote) + GtkTreeSelection *selection; + gtk_list_store_append (ls, &iter); + quote_tmp = purple_markup_strip_html (quote); + g_string_printf (s, "%ld bytes", g_utf8_strlen (quote, -1)); + gtk_list_store_set (ls, &iter, + g_string_free (s, TRUE); + treeview = (GtkWidget *) ap_widget_get_data (w, QUOTATION_TREE_VIEW); + if (treeview == NULL) return; + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(treeview)); + gtk_tree_selection_select_iter (selection, &iter); +static void file_dialog_cb (GtkWidget *dialog, int response, struct widget *w) + GList *quotes, *quotes_start, *new_quotes; + case GTK_RESPONSE_ACCEPT: + ls = ap_widget_get_data (w, QUOTATION_LIST_STORE); + filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER(dialog)); + checkbox = gtk_file_chooser_get_extra_widget (GTK_FILE_CHOOSER(dialog)); + g_object_get (checkbox, "active", &include_html, NULL); + quotes = ap_prefs_get_string_list (w, "quotes"); + new_quotes = read_fortune_file (filename, !include_html); + quotes = g_list_concat (quotes, new_quotes); + ap_prefs_set_string_list (w, "quotes", quotes); + for (quotes = new_quotes; quotes != NULL; quotes = quotes->next) { + append_quote (w, ls, quotes->data); + free_string_list (quotes_start); + case GTK_RESPONSE_CANCEL: + case GTK_RESPONSE_DELETE_EVENT: + ap_widget_set_data (w, QUOTATION_FILE_SELECTOR, NULL); + gtk_widget_destroy (dialog); +static void quotation_explain_fortune_file (GtkMenuItem *item, gpointer data) + purple_notify_formatted (NULL, _("Fortune files"), + _("A quick definition of a fortune file"), NULL, + _("A fortune file is a simple text file with a number of quotes. " + "The following is an example:<br><br>" + "<b>\"Glory is fleeing, but obscurity is forver.\"<br>" + "- Napoleon Bonaparte (1769-1821)<br>" + "Yet another quote<br>" + "Quotes can have any sort of text within them. They end when there " + "is a newline followed by a percent sign \"%\" on the next line.<br>" + "<br>Fortune files with pre-selected quotes can be found on the" +static void quotation_select_import_file (GtkMenuItem *item, struct widget *w) + dialog = gtk_file_chooser_dialog_new ( + _("Select fortune file to import quotes from"), + GTK_FILE_CHOOSER_ACTION_OPEN, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, + g_signal_connect (G_OBJECT(dialog), "response", G_CALLBACK (file_dialog_cb), + ap_widget_set_data (w, QUOTATION_FILE_SELECTOR, dialog); + checkbox = gtk_check_button_new_with_label ( + _("Interpret bracketed text (such as \"<br>\") as HTML tags")); + gtk_file_chooser_set_extra_widget (GTK_FILE_CHOOSER(dialog), checkbox); + gtk_widget_show_all (dialog); +static void quotation_edit_dialog_cb (struct widget *w, const char *quote) + GtkTreeSelection *selection; + treeview = (GtkWidget *) ap_widget_get_data (w, QUOTATION_TREE_VIEW); + if (treeview == NULL) return; + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(treeview)); + if (gtk_tree_selection_get_selected (selection, &model, &iter)) { + gchar *quote_tmp, *old_quote; + gtk_tree_model_get (model, &iter, 1, &old_quote, -1); + start = ap_prefs_get_string_list (w, "quotes"); + /* FIXME: this could grab the wrong quote, if two quotes are identical */ + for (node = start; node != NULL; node = node->next) { + if (!strcmp ((char *) node->data, old_quote)) { + /* Update saved prefs */ + node->data = strdup (quote); + ap_prefs_set_string_list (w, "quotes", start); + free_string_list (start); + /* Update list store */ + quote_tmp = purple_markup_strip_html (quote); + g_string_printf (s, "%ld bytes", g_utf8_strlen (quote, -1)); + gtk_list_store_set (GTK_LIST_STORE (model), &iter, + g_string_free (s, TRUE); + free_string_list (start); + purple_notify_error (NULL, NULL, + N_("Unable to edit quote"), + N_("No quote is currently selected")); +static void quotation_edit_dialog (struct widget *w, const gchar *quote) + purple_request_input (ap_get_plugin_handle (), NULL, + _("Save"), G_CALLBACK(quotation_edit_dialog_cb), + _("Cancel"), NULL, NULL, NULL, NULL, +static void quotation_edit (GtkWidget *button, struct widget *w) + GtkTreeSelection *selection; + treeview = (GtkWidget *) ap_widget_get_data (w, QUOTATION_TREE_VIEW); + if (treeview == NULL) return; + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(treeview)); + if (gtk_tree_selection_get_selected (selection, &model, &iter)) { + gtk_tree_model_get (model, &iter, 1, "e, -1); + quotation_edit_dialog (w, quote); + purple_notify_error (NULL, NULL, + N_("Unable to edit quote"), + N_("No quote is currently selected")); +static void quotation_create (GtkWidget *button, struct widget *w) + ls = ap_widget_get_data (w, QUOTATION_LIST_STORE); + if (ls == NULL) return; + append_quote (w, ls, ""); + quotes = ap_prefs_get_string_list (w, "quotes"); + quotes = g_list_append (quotes, strdup ("")); + ap_prefs_set_string_list (w, "quotes", quotes); + free_string_list (quotes); + quotation_edit_dialog (w, ""); +static void quotation_delete (GtkWidget *button, struct widget *w) + GtkTreeSelection *selection; + treeview = (GtkWidget *) ap_widget_get_data (w, QUOTATION_TREE_VIEW); + if (treeview == NULL) return; + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(treeview)); + if (gtk_tree_selection_get_selected (selection, &model, &iter)) { + gtk_tree_model_get (model, &iter, 1, "e, -1); + start = ap_prefs_get_string_list (w, "quotes"); + /* FIXME: this could grab the wrong quote, if two quotes are identical */ + for (node = start; node != NULL; node = node->next) { + if (!strcmp ((char *) node->data, quote)) { + start = g_list_remove_link (start, node); + ap_prefs_set_string_list (w, "quotes", start); + free_string_list (start); + gtk_list_store_remove (GTK_LIST_STORE(model), &iter); + free_string_list (start); + purple_notify_error (NULL, NULL, + N_("Unable to delete quote"), + N_("No quote is currently selected")); +static void quotation_delete_all_cb (struct widget *w) + ls = ap_widget_get_data (w, QUOTATION_LIST_STORE); + if (ls == NULL) return; + gtk_list_store_clear (ls); + ap_prefs_set_string_list (w, "quotes", NULL); +static void quotation_delete_all (GtkMenuItem *item, struct widget *w) + purple_request_ok_cancel (ap_get_plugin_handle (), + NULL, _("Delete all quotes?"), NULL, 0, NULL, NULL, + NULL, w, G_CALLBACK(quotation_delete_all_cb), NULL); +static void quotation_more_menu (GtkWidget *button, struct widget *w) + menu = gtk_menu_new (); + menu_item = gtk_menu_item_new_with_label (_("Delete all quotes")); + gtk_menu_shell_append (GTK_MENU_SHELL(menu), menu_item); + g_signal_connect (G_OBJECT(menu_item), "activate", + G_CALLBACK(quotation_delete_all), w); + menu_item = gtk_separator_menu_item_new (); + gtk_menu_shell_append (GTK_MENU_SHELL(menu), menu_item); + menu_item = gtk_menu_item_new_with_label ( + _("Import quotes from from fortune file")); + gtk_menu_shell_append (GTK_MENU_SHELL(menu), menu_item); + g_signal_connect (G_OBJECT(menu_item), "activate", + G_CALLBACK(quotation_select_import_file), w); + menu_item = gtk_menu_item_new_with_label ( + _("What is a fortune file?")); + gtk_menu_shell_append (GTK_MENU_SHELL(menu), menu_item); + g_signal_connect (G_OBJECT(menu_item), "activate", + G_CALLBACK(quotation_explain_fortune_file), NULL); + gtk_menu_popup (GTK_MENU(menu), NULL, NULL, NULL, w, 0, + gtk_get_current_event_time ()); + gtk_widget_show_all (menu); +static void quotation_rate_changed (GtkSpinButton *spinner, struct widget *w) + int value = gtk_spin_button_get_value_as_int (spinner); + ap_prefs_set_int (w, "update_rate", value); +static void quotation_force_change (GtkButton *button, struct widget *w) + ap_prefs_set_int (w, "current_index", + ap_prefs_get_int (w, "current_index") + 1); +search_func(GtkTreeModel *model, gint column, const gchar *key, + GtkTreeIter *iter, gpointer search_data) + gtk_tree_model_get (model, iter, 1, &haystack, -1); + result = (purple_strcasestr(haystack, key) == NULL); +static void menu_destroy_cb (GtkWidget *widget, struct widget *w) + GtkWidget *file_selector; + ap_widget_set_data (w, QUOTATION_LIST_STORE, NULL); + ap_widget_set_data (w, QUOTATION_TREE_VIEW, NULL); + file_selector = (GtkWidget *) ap_widget_get_data (w, QUOTATION_FILE_SELECTOR); + if (file_selector != NULL) { + file_dialog_cb (file_selector, GTK_RESPONSE_DELETE_EVENT, w); +static GtkWidget *quotation_menu (struct widget *w) + GtkWidget *button, *label, *spinner; + GList *quotes, *quotes_start; + GtkTreeViewColumn *col; + ret = gtk_vbox_new (FALSE, 6); + g_signal_connect (G_OBJECT(ret), "destroy", G_CALLBACK (menu_destroy_cb), w); + sw = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(sw), + GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(sw), + gtk_box_pack_start (GTK_BOX(ret), sw, TRUE, TRUE, 0); + ls = gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING); + ap_widget_set_data (w, QUOTATION_LIST_STORE, ls); + treeview = gtk_tree_view_new_with_model (GTK_TREE_MODEL(ls)); + ap_widget_set_data (w, QUOTATION_TREE_VIEW, treeview); + rend = gtk_cell_renderer_text_new (); + col = gtk_tree_view_column_new_with_attributes (_("Size"), + rend, "text", 2, NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW(treeview), col); + g_object_set (G_OBJECT(rend), + "cell-background-set", TRUE, + "cell-background", "gray", + rend = gtk_cell_renderer_text_new (); + col = gtk_tree_view_column_new_with_attributes (_("Quotes"), + rend, "text", 0, NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW(treeview), col); + /* Enable CTRL+F searching */ + gtk_tree_view_set_search_column (GTK_TREE_VIEW(treeview), 0); + gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW(treeview), + search_func, NULL, NULL); + gtk_container_add (GTK_CONTAINER(sw), treeview); + /* Add in the original quotes */ + quotes_start = ap_prefs_get_string_list (w, "quotes"); + for (quotes = quotes_start; quotes != NULL; quotes = quotes->next) { + append_quote (w, ls, quotes->data); + free_string_list (quotes_start); + gtk_tree_view_set_rules_hint (GTK_TREE_VIEW(treeview), TRUE); + hbox = gtk_hbutton_box_new (); + gtk_button_box_set_layout (GTK_BUTTON_BOX(hbox), + gtk_box_pack_start (GTK_BOX(ret), hbox, FALSE, FALSE, 0); + button = gtk_button_new_with_label (_("New quote")); + gtk_box_pack_start (GTK_BOX(hbox), button, FALSE, FALSE, 0); + g_signal_connect (G_OBJECT(button), "clicked", + G_CALLBACK(quotation_create), w); + button = gtk_button_new_with_label (_("Edit")); + gtk_box_pack_start (GTK_BOX(hbox), button, FALSE, FALSE, 0); + g_signal_connect (G_OBJECT(button), "clicked", + G_CALLBACK(quotation_edit), w); + button = gtk_button_new_with_label (_("Delete")); + gtk_box_pack_start (GTK_BOX(hbox), button, FALSE, FALSE, 0); + g_signal_connect (G_OBJECT(button), "clicked", + G_CALLBACK(quotation_delete), w); + button = gtk_button_new_with_label (_("More...")); + gtk_box_pack_start (GTK_BOX(hbox), button, FALSE, FALSE, 0); + g_signal_connect (G_OBJECT(button), "clicked", + G_CALLBACK(quotation_more_menu), w); + gtk_box_pack_start (GTK_BOX(ret), gtk_hseparator_new (), FALSE, FALSE, 0); + /* Behavior selection */ + hbox = gtk_hbox_new (FALSE, 5); + gtk_box_pack_start (GTK_BOX(ret), hbox, FALSE, FALSE, 0); + label = gtk_label_new (_("Change quote every ")); + gtk_box_pack_start (GTK_BOX(hbox), label, FALSE, FALSE, 0); + spinner = gtk_spin_button_new_with_range (0, G_MAXINT, 1); + gtk_box_pack_start (GTK_BOX(hbox), spinner, FALSE, FALSE, 0); + gtk_spin_button_set_value (GTK_SPIN_BUTTON(spinner), + ap_prefs_get_int (w, "update_rate")); + g_signal_connect (G_OBJECT(spinner), "value-changed", + G_CALLBACK(quotation_rate_changed), w); + label = gtk_label_new (_("hours (0: always show a new quote)")); + gtk_box_pack_start (GTK_BOX(hbox), label, FALSE, FALSE, 0); + button = gtk_button_new_with_label (_("Change quote now")); + gtk_box_pack_end (GTK_BOX(hbox), button, FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (button), "clicked", + G_CALLBACK (quotation_force_change), w); +/*--------------------------------------------------------------------------* + * Core quotation things * + *--------------------------------------------------------------------------*/ +static gchar *quotation_generate (struct widget *w) + time_t cur_time, old_time; + index = ap_prefs_get_int (w, "current_index"); + quotes = ap_prefs_get_string_list (w, "quotes"); + /* Sanity check the quotes */ + num_quotes = g_list_length (quotes); + return strdup (_("[ERROR: no quotes available]")); + /* Increment index if time has elapsed */ + old_time = purple_str_to_time (ap_prefs_get_string (w, "last_update"), TRUE, + cur_time = time (NULL); + if (difftime (cur_time, old_time) > + 60.0 * 60.0 * (double) ap_prefs_get_int (w, "update_rate")) + ap_debug ("quote", "time interval elapsed, moving to new quote"); + time_string = (char *)malloc(1000); + t = ap_gmtime (&cur_time); + strftime (time_string, 999, "%Y-%m-%dT%H:%M:%S+00:00", t); + ap_prefs_set_string (w, "last_update", time_string); + ap_prefs_set_int (w, "current_index", index); + /* Wrap around when last quote is reached */ + if (index >= num_quotes) { + ap_prefs_set_int (w, "current_index", 0); + /* Choose and output the quote */ + ret = strdup((gchar *) g_list_nth_data (quotes, index)); + free_string_list (quotes); +static void quotation_init (struct widget *w) + time_string = (char *)malloc(1000); + strftime (time_string, 999, "%Y-%m-%dT%H:%M:%S+00:00", gmtime (&the_time)); + ap_prefs_add_string_list (w, "quotes", NULL); + ap_prefs_add_int (w, "current_index", 0); + ap_prefs_add_int (w, "update_rate", 0); + ap_prefs_add_string (w, "last_update", time_string); +struct component quotation = + N_("Displays a quotation from a provided selection"), --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/autoprofile/comp_rss.c Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,477 @@
+/*--------------------------------------------------------------------------* + * A Purple away message and profile manager that supports dynamic text * + * AutoProfile is the legal property of its developers. 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 "autoprofile.h" +static GtkWidget *entry_username = NULL; +static GtkWidget *entry_url = NULL; +GHashTable *rss_entries = NULL; +static GHashTable *rss_timeouts = NULL; +GStaticMutex rss_mutex = G_STATIC_MUTEX_INIT; +static char *get_rss_data (struct widget *w, const char *field, int index, + const struct rss_entry *e; + g_static_mutex_lock (&rss_mutex); + tmp = (GList *) g_hash_table_lookup (rss_entries, w); + g_static_mutex_unlock (&rss_mutex); + return strdup (_("[ERROR: Invalid entry number]")); + g_static_mutex_unlock (&rss_mutex); + return strdup (_("[ERROR: No data, invalid URL/account?]")); + g_static_mutex_unlock (&rss_mutex); + return strdup (_("[ERROR: Insufficient number of entries]")); + e = (struct rss_entry *) tmp->data; + if (!strcmp (field, "link")) { + } else if (!strcmp (field, "title")) { + ret = strdup (e->title); + } else if (!strcmp (field, "entry")) { + max = ap_prefs_get_int (w, "entry_limit"); + ret = strdup (e->entry); + if (max < g_utf8_strlen (ret, -1)) { + gchar *tmp = g_utf8_offset_to_pointer (ret, max); + } else if (!strcmp (field, "time")) { + g_static_mutex_unlock (&rss_mutex); +static char *rss_generate (struct widget *w) + format = ap_prefs_get_string (w, "format"); + output = g_string_new (""); + time_tmp = (char *)malloc (sizeof(char)*AP_SIZE_MAXIMUM); + if (isdigit (*format)) { + count = (count * 10) + (int) *format - 48; + tmp = get_rss_data (w, "time", count, &time); + strftime (time_tmp, AP_SIZE_MAXIMUM, fmt_char, time); + g_string_append_printf (output, "%s", time_tmp); + tmp = get_rss_data (w, "link", count, NULL); + g_string_append_printf (output, "%s", tmp); + tmp = get_rss_data (w, "title", count, NULL); + g_string_append_printf (output, "%s", tmp); + tmp = get_rss_data (w, "entry", count, NULL); + g_string_append_printf (output, "%s", tmp); + g_string_append_printf (output, "%c", *format); + g_string_append_unichar (output, g_utf8_get_char (format)); + format = g_utf8_next_char (format); + g_string_append_unichar (output, g_utf8_get_char (format)); + format = g_utf8_next_char (format); + g_string_free (output, FALSE); +static gboolean rss_update (gpointer data) + parse_rss ((struct widget *) data); +static void rss_load (struct widget *w) + g_static_mutex_lock (&rss_mutex); + rss_entries = g_hash_table_new (NULL, NULL); + rss_timeouts = g_hash_table_new (NULL, NULL); + rss_timeout = GINT_TO_POINTER (g_timeout_add ( + ap_prefs_get_int (w, "update_rate") * 60 * 1000, + g_hash_table_insert (rss_timeouts, w, rss_timeout); + g_static_mutex_unlock (&rss_mutex); +static void rss_unload (struct widget *w) + g_static_mutex_lock (&rss_mutex); + rss_timeout = g_hash_table_lookup (rss_timeouts, w); + g_source_remove (GPOINTER_TO_INT (rss_timeout)); + g_hash_table_remove (rss_timeouts, w); + g_static_mutex_unlock (&rss_mutex); +static void rss_init (struct widget *w) + ap_prefs_add_int (w, "type", RSS_XANGA); + ap_prefs_add_string (w, "location", ""); + ap_prefs_add_string (w, "username", ""); + ap_prefs_add_string (w, "format", + "My <a href=\"%l\">blog</a> was most recently updated on " + "%1B %1d at %I:%M %p"); + ap_prefs_add_int (w, "update_rate", 5); + ap_prefs_add_int (w, "entry_limit", 1000); +static gboolean update_refresh_rate (GtkWidget *widget, GdkEventFocus *evt, + minutes = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (widget)); + ap_prefs_set_int (w, "update_rate", minutes); + // Kill the current timer and run a new one + g_static_mutex_lock (&rss_mutex); + timeout = g_hash_table_lookup (rss_timeouts, w); + g_source_remove (GPOINTER_TO_INT(timeout)); + timeout = GINT_TO_POINTER (g_timeout_add (minutes * 60 * 1000, + g_hash_table_replace (rss_timeouts, w, timeout); + g_static_mutex_unlock (&rss_mutex); +static void rss_data_update (GtkWidget *widget, struct widget *w) +static void sensitivity_cb (const char *name, PurplePrefType type, + gconstpointer val, gpointer data) + int real_val = GPOINTER_TO_INT (val); + if (real_val == RSS_XANGA || real_val == RSS_LIVEJOURNAL) { + gtk_widget_set_sensitive (entry_username, TRUE); + gtk_widget_set_sensitive (entry_url, FALSE); + gtk_widget_set_sensitive (entry_username, FALSE); + gtk_widget_set_sensitive (entry_url, TRUE); +static GtkWidget *entry; +static void event_cb (GtkWidget *widget, struct widget *w) + ap_prefs_set_string (w, "format", + gtk_imhtml_get_markup (GTK_IMHTML(entry))); +static void formatting_toggle_cb (GtkIMHtml *imhtml, + GtkIMHtmlButtons buttons, struct widget *w) + ap_prefs_set_string (w, "format", + gtk_imhtml_get_markup (GTK_IMHTML(entry))); +static void formatting_clear_cb (GtkIMHtml *imhtml, + ap_prefs_set_string (w, "format", + gtk_imhtml_get_markup (GTK_IMHTML(entry))); +static GtkWidget *rss_menu (struct widget *w) + GtkWidget *label, *hbox, *button, *spinner, *sw; + GtkWidget *entry_window, *toolbar; + GtkTextBuffer *text_buffer; + ret = gtk_vbox_new (FALSE, 5); + label = gtk_label_new (NULL); + gtk_label_set_markup (GTK_LABEL(label), "<b>Format string for output</b>"); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + gtk_box_pack_start (GTK_BOX(ret), label, FALSE, FALSE, 0); + hbox = gtk_hbox_new (FALSE, 5); + gtk_box_pack_start (GTK_BOX(ret), hbox, TRUE, TRUE, 0); + entry_window = pidgin_create_imhtml (TRUE, &entry, &toolbar, &sw); + gtk_box_pack_start (GTK_BOX (hbox), entry_window, TRUE, TRUE, 0); + gtk_imhtml_append_text_with_images (GTK_IMHTML(entry), + ap_prefs_get_string (w, "format"), + text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (entry)); + g_signal_connect (G_OBJECT (text_buffer), "changed", + G_CALLBACK (event_cb), w); + g_signal_connect_after(G_OBJECT(entry), "format_function_toggle", + G_CALLBACK(formatting_toggle_cb), w); + g_signal_connect_after(G_OBJECT(entry), "format_function_clear", + G_CALLBACK(formatting_clear_cb), w); + label = gtk_label_new (_( + "The following options can be specified with a numerical modifier\n" + "(i.e. \"%e\" can also be written \"%1e\" or \"%2e\"). The optional\n" + "number specifies which entry to get the data for. \"1\" refers to the\n" + "most recent entry, \"2\" refers to the second-most recent entry, and so\n" + "forth. \"1\" is used if no number is specified.\n\n" + "%e\tStarting text of the entry.\n" + "%l\tLink to the specific entry.\n" + "%t\tTitle of entry (Xanga incompatible)\n" + "%H\thour of entry(24-hour clock)\n" + "%I\thour (12-hour clock)\n" + "%a\tabbreviated weekday name\n" + "%A\tfull weekday name\n" + "%b\tabbreviated month name\n" + "%B\tfull month name\n" + "%m\tmonth (numerical)\n" + "%d\tday of the month\n" + "%j\tday of the year\n" + "%W\tweek number of the year\n" + "%w\tweekday (numerical)\n" + "%y\tyear without century\n" + "%Y\tyear with century\n" + "%z\ttime zone name, if any\n" + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + sw = gtk_scrolled_window_new (NULL,NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), + GTK_POLICY_NEVER, GTK_POLICY_ALWAYS); + gtk_box_pack_start (GTK_BOX (hbox), sw, TRUE, TRUE , 0); + gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW(sw), label); + /* Type/URL/Username selection */ + label = gtk_label_new (NULL); + gtk_label_set_markup (GTK_LABEL(label), "<b>RSS/Blog location</b>"); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + gtk_box_pack_start (GTK_BOX(ret), label, FALSE, FALSE, 0); + hbox = gtk_hbox_new (FALSE, 5); + gtk_box_pack_start (GTK_BOX(ret), hbox, FALSE, FALSE, 0); + options = g_list_append (NULL, (char *) _("Xanga")); + options = g_list_append (options, GINT_TO_POINTER(RSS_XANGA)); + options = g_list_append (options, (char *) _("LiveJournal")); + options = g_list_append (options, GINT_TO_POINTER(RSS_LIVEJOURNAL)); + options = g_list_append (options, (char *) _("RSS 2.0")); + options = g_list_append (options, GINT_TO_POINTER(RSS_2)); + ap_prefs_dropdown_from_list (w, hbox, NULL, PURPLE_PREF_INT, "type", options); + pref = ap_prefs_get_pref_name (w, "type"); + purple_prefs_connect_callback (ap_get_plugin_handle (), pref, + /* Username/URL fields */ + entry_username = ap_prefs_labeled_entry (w, hbox, _("Username:"), + entry_url = ap_prefs_labeled_entry (w, hbox, _("URL of feed:"), + value = ap_prefs_get_int (w, "type"); + if (value == RSS_XANGA || value == RSS_LIVEJOURNAL) { + gtk_widget_set_sensitive (entry_username, TRUE); + gtk_widget_set_sensitive (entry_url, FALSE); + gtk_widget_set_sensitive (entry_username, FALSE); + gtk_widget_set_sensitive (entry_url, TRUE); + label = gtk_label_new (NULL); + gtk_label_set_markup (GTK_LABEL(label), "<b>Other options</b>"); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + gtk_box_pack_start (GTK_BOX(ret), label, FALSE, FALSE, 0); + /* # of chars to display from description */ + ap_prefs_labeled_spin_button (w, ret, + "Max characters to show in entry (%e)", "entry_limit", 1, + AP_SIZE_MAXIMUM - 1, NULL); + /* Update rate selection */ + hbox = gtk_hbox_new (FALSE, 5); + gtk_box_pack_start (GTK_BOX (ret), hbox, FALSE, FALSE, 0); + label = gtk_label_new (_("Minutes between checks for updates:")); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + spinner = gtk_spin_button_new_with_range (1, 60, 1); + gtk_box_pack_start (GTK_BOX(hbox), spinner, FALSE, FALSE, 0); + gtk_spin_button_set_value (GTK_SPIN_BUTTON (spinner), + ap_prefs_get_int (w, "update_rate")); + g_signal_connect (G_OBJECT (spinner), "value-changed", + G_CALLBACK (update_refresh_rate), w); + button = gtk_button_new_with_label ("Fetch data now!"); + g_signal_connect (G_OBJECT (button), "clicked", + G_CALLBACK (rss_data_update), w); + gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0); + N_("Information taken from an RSS feed (Xanga and LiveJournal capable)"), --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/autoprofile/comp_rss.h Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,52 @@
+/*--------------------------------------------------------------------------* + * A Purple away message and profile manager that supports dynamic text * + * AutoProfile is the legal property of its developers. 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 MAX_USERNAME_LENGTH 1000 +extern GHashTable *rss_entries; +extern GStaticMutex rss_mutex; +void parse_rss (struct widget *); +void parse_xanga_rss (struct widget *, gchar *); +GMarkupParser rss_parser; --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/autoprofile/comp_rss_parser.c Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,350 @@
+/*--------------------------------------------------------------------------* + * A Purple away message and profile manager that supports dynamic text * + * AutoProfile is the legal property of its developers. 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 * + *--------------------------------------------------------------------------*/ +static gboolean parsing_rss = FALSE; +static gboolean parsing_item = FALSE; +static gboolean item_title = FALSE; +static gboolean item_link = FALSE; +static gboolean item_description = FALSE; +static gboolean item_comments = FALSE; +static gboolean item_pubdate = FALSE; +/* Get URL of RSS feed */ +static char *get_url (struct widget *w) + type = ap_prefs_get_int (w, "type"); + username = ap_prefs_get_string (w, "username"); + g_string_append_printf (s, + "http://www.livejournal.com/users/%s/data/rss", username); + username = ap_prefs_get_string (w, "username"); + g_string_append_printf (s, "http://www.xanga.com/%s/rss", username); + g_string_append_printf (s, "%s", ap_prefs_get_string (w, "location")); + g_string_free (s, FALSE); +static void free_entries (struct widget *w) { + entries = (GList *) g_hash_table_lookup (rss_entries, w); + e = (struct rss_entry *) entries->data; + entries = entries->next; + g_hash_table_replace (rss_entries, w, NULL); +/* Date parsing functions */ +static struct tm *parse_date_rfc822 (const char *date_string) + time_t t, gmt_time, local_time; + struct tm *ret, *result; + local_time = time(NULL); + gmt_time = mktime(gmtime(&gmt_time)); + // TODO: Change this to GDate + t = rfc_parse_date_time (date_string); + // if (rfc_parse_was_gmt ()) { + // FIXME: Handle time zones + ret = (struct tm *) malloc (sizeof (struct tm)); + result = localtime(&t); + ret->tm_sec = result->tm_sec; + ret->tm_min = result->tm_min; + ret->tm_hour = result->tm_hour; + ret->tm_mday = result->tm_mday; + ret->tm_mon = result->tm_mon; + ret->tm_year = result->tm_year; +/* XML Parser Callbacks */ +static void start_element_handler (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribuate_names, + const gchar **attribute_values, + gpointer data, GError **error) + struct widget *w = (struct widget *) data; + //printf ("start:%s\n", element_name); + if (!parsing_rss && !strcmp (element_name, "rss")) + else if (parsing_rss && !parsing_item && + !strcmp (element_name, "item")) { + e = (struct rss_entry *) malloc (sizeof(struct rss_entry)); + entries = (GList *) g_hash_table_lookup (rss_entries, w); + entries = g_list_prepend (entries, e); + g_hash_table_replace (rss_entries, w, entries); + else if (parsing_item && !strcmp (element_name, "title")) + else if (parsing_item && !strcmp (element_name, "link")) + else if (parsing_item && !strcmp (element_name, "description")) + item_description = TRUE; + else if (parsing_item && !strcmp (element_name, "comments")) + else if (parsing_item && !strcmp (element_name, "pubDate")) +static void end_element_handler (GMarkupParseContext *context, + const gchar *element_name, + gpointer data, GError **error) + struct widget *w = (struct widget *) w; + //printf ("end:%s\n", element_name); + if (!strcmp (element_name, "rss")) + else if (!strcmp (element_name, "item")) + else if (!strcmp (element_name, "title")) + else if (!strcmp (element_name, "link")) + else if (!strcmp (element_name, "description")) + item_description = FALSE; + else if (!strcmp (element_name, "comments")) + else if (!strcmp (element_name, "pubDate")) +static void text_handler (GMarkupParseContext *context, + const gchar *text, gsize text_len, + gpointer data, GError **error) + struct widget *w = (struct widget *) data; + entries = (GList *) g_hash_table_lookup (rss_entries, w); + e = (struct rss_entry *) entries->data; + e->url = g_strdup (text); + else if (item_description) { + e->entry = purple_unescape_html (text); + // If there is a standard format for Xanga titles (there really isn't) + // it will probably be devised from the actual content. Will be placed + // here if there is proven demand. + else if (item_comments) { + e->comments = g_strdup (text); + e->title = g_strdup (text); + else if (item_pubdate) { + e->t = parse_date_rfc822 (text); +/* Final parser variable */ +GMarkupParser rss_parser = +/* Callback for HTTP data fetcher */ +static void url_callback (gpointer data, const char *text, size_t size) + GMarkupParseContext *context; + struct widget *w = (struct widget *) data; + /* Make sure URL exists/connected to Internet */ + ap_debug ("rss", "error, unable to fetch page via internet"); + item_description = FALSE; + g_static_mutex_lock (&rss_mutex); + filtered_text = purple_utf8_salvage (text); + gchar *convert = purple_utf8_try_convert ("<"); + gchar *next = g_utf8_strchr (filtered_text, 10, g_utf8_get_char (convert)); + g_static_mutex_unlock (&rss_mutex); + if (ap_prefs_get_int (w, "type") == RSS_XANGA) { + parse_xanga_rss (w, filtered_text); + entries = (GList *) g_hash_table_lookup (rss_entries, w); + entries = g_list_reverse (entries); + g_hash_table_replace (rss_entries, w, entries); + g_static_mutex_unlock (&rss_mutex); + context = g_markup_parse_context_new (&rss_parser, 0, w, NULL); + if (!g_markup_parse_context_parse (context, next, size, &err)) { + g_markup_parse_context_free (context); + ap_debug ("rss", "error, unable to start parser"); + ap_debug ("rss", err->message); + if (!g_markup_parse_context_end_parse (context, &err)) { + g_markup_parse_context_free (context); + ap_debug ("rss", "error, unable to end parser"); + g_markup_parse_context_free (context); + entries = (GList *) g_hash_table_lookup (rss_entries, w); + entries = g_list_reverse (entries); + g_hash_table_replace (rss_entries, w, entries); + g_static_mutex_unlock (&rss_mutex); +void parse_rss (struct widget *w) + if (strcmp (url, "") != 0) { + purple_util_fetch_url (url, TRUE, NULL, FALSE, url_callback, w); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/autoprofile/comp_rss_xanga.c Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,117 @@
+/*--------------------------------------------------------------------------* + * A Purple away message and profile manager that supports dynamic text * + * AutoProfile is the legal property of its developers. 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 * + *--------------------------------------------------------------------------*/ +static gchar *convert_char; +static gchar *find_next_tag (gchar *text) { + return g_utf8_strchr (text, -1, g_utf8_get_char (convert_char)); +static gchar *find_end_of_tag (gchar *text) { + return g_utf8_strchr (text, -1, g_utf8_get_char (convert_char)); +static gboolean starts_with (gchar *text, gchar letter) { + *convert_char = letter; + return (g_utf8_strchr (text, 1, g_utf8_get_char (convert_char)) == text); +void parse_xanga_rss (struct widget *w, gchar *text) { + gchar *next_tag, *end_prev_tag; + gchar *tag_first_char, *tag_second_char; + convert_char = (gchar *) malloc (sizeof(gchar) * 2); + *(convert_char+1) = '\0'; + rss_parser.start_element (NULL, "rss", NULL, NULL, w, NULL); + while ((next_tag = find_next_tag (end_prev_tag)) != NULL) { + tag_first_char = g_utf8_next_char (next_tag); + tag_second_char = g_utf8_next_char (tag_first_char); + if (starts_with (tag_first_char, 't')) rss_parser.start_element ( + NULL, "title", NULL, NULL, w, NULL); + else if (starts_with (tag_first_char, 'l')) rss_parser.start_element ( + NULL, "link", NULL, NULL, w, NULL); + else if (starts_with (tag_first_char, 'p')) rss_parser.start_element ( + NULL, "pubDate", NULL, NULL, w, NULL); + else if (starts_with (tag_first_char, 'd')) rss_parser.start_element ( + NULL, "description", NULL, NULL, w, NULL); + else if (starts_with (tag_first_char, 'c')) rss_parser.start_element ( + NULL, "comments", NULL, NULL, w, NULL); + else if (starts_with (tag_first_char, '/')) { + rss_parser.text (NULL, end_prev_tag, -1, w, NULL); + if (starts_with (tag_second_char, 't')) + rss_parser.end_element (NULL, "title", w, NULL); + else if (starts_with (tag_second_char, 'l')) + rss_parser.end_element (NULL, "link", w, NULL); + else if (starts_with (tag_second_char, 'p')) + rss_parser.end_element (NULL, "pubDate", w, NULL); + else if (starts_with (tag_second_char, 'd')) + rss_parser.end_element (NULL, "description", w, NULL); + else if (starts_with (tag_second_char, 'c')) + rss_parser.end_element (NULL, "comments", w, NULL); + else if (starts_with (tag_second_char, 'i')) { + rss_parser.end_element (NULL, "item", w, NULL); + // TODO: WARN USER IN THIS CASE + if (starts_with (tag_first_char, 'i') && + starts_with (tag_second_char, 't')) { + rss_parser.start_element (NULL, "item", NULL, NULL, w, NULL); + if ((next_tag = find_end_of_tag (tag_first_char)) == NULL) { + // TODO: NOTIFY USER THAT WE REACHED END + end_prev_tag = g_utf8_next_char (next_tag); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/autoprofile/comp_textfile.c Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,268 @@
+/*--------------------------------------------------------------------------* + * A Purple away message and profile manager that supports dynamic text * + * AutoProfile is the legal property of its developers. 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 "../common/pp_internal.h" +/*---------- TEXT FILE: Text from a file ----------*/ +static GtkWidget *file_entry; +static GtkWidget *file_selector; +/* Read file into string and return */ +char *text_file_generate (struct widget *w) + gchar *text, *salvaged, *converted; + int max = ap_prefs_get_int (w, "text_size"); + filename = ap_prefs_get_string (w, "text_file"); + if (!g_file_test (filename, G_FILE_TEST_EXISTS)) { + return g_strdup (_("[ERROR: File does not exist]")); + if (!g_file_get_contents (filename, &text, NULL, NULL)) { + return g_strdup (_("[ERROR: Unable to open file]")); + converted = purple_utf8_try_convert (text); + if (converted != NULL) { + if (strlen (text) > max) { + salvaged = purple_utf8_salvage (text); +static gboolean text_file_update (GtkWidget *widget, GdkEventFocus *evt, + ap_prefs_set_string (w, "text_file", + gtk_entry_get_text (GTK_ENTRY (file_entry))); +static void text_file_filename (GtkWidget *widget, gpointer user_data) { + const gchar *selected_filename; + struct widget *w = (struct widget *) user_data; + selected_filename = gtk_file_selection_get_filename ( + GTK_FILE_SELECTION (file_selector)); + ap_prefs_set_string (w, "text_file", selected_filename); + gtk_entry_set_text (GTK_ENTRY (file_entry), selected_filename); +/* Creates and pops up file selection dialog for fortune file */ +static void text_file_selection (GtkWidget *widget, gpointer user_data) { + struct widget *w = (struct widget *) user_data; + /* Create the selector */ + file_selector = gtk_file_selection_new ( + "Select a text file with content"); + cur_file = ap_prefs_get_string (w, "text_file"); + if (cur_file && strlen (cur_file) > 1) { + gtk_file_selection_set_filename ( + GTK_FILE_SELECTION (file_selector), cur_file); + g_signal_connect (GTK_OBJECT( + GTK_FILE_SELECTION(file_selector)->ok_button), + "clicked", G_CALLBACK (text_file_filename), w); + /* Destroy dialog box when the user clicks button. */ + g_signal_connect_swapped (GTK_OBJECT( + GTK_FILE_SELECTION(file_selector)->ok_button), + "clicked", G_CALLBACK (gtk_widget_destroy), (gpointer) file_selector); + g_signal_connect_swapped (GTK_OBJECT ( + GTK_FILE_SELECTION (file_selector)->cancel_button), + "clicked", G_CALLBACK (gtk_widget_destroy), (gpointer) file_selector); + gtk_widget_show (file_selector); +/* Pop up message with instructions */ +static void text_file_info_button (GtkButton *button, gpointer data) + if (!strcmp ((char *) data, "itunes")) { + purple_notify_formatted (NULL, _("iTunes"), _("Current song in iTunes"), NULL, + _("Get TuneCam from <a href=\"" + "http://www.soft-o-mat.com/productions.shtml\">" + "http://www.soft-o-mat.com/productions.shtml</a> and start it.<br>" + "Create a html file that contains the following text:<br><br><tc" + ">artist</tc> - <tc>title</tc><br><br>and " + "press the \"T\" button. Import the html file as a template for " + "the \"File Track\" and whatever else you see fit. Then select " + "the \"G\" button and choose the location of the output file which " + "will be used in this component"), + } else if (!strcmp ((char *) data, "xmms")) { + purple_notify_formatted (NULL, _("XMMS"), _("Current song in XMMS"), NULL, + _("Included in the misc folder of AutoProfile is a script called \"" + "xmms_currenttrack\". Install this script in your $PATH and give it " + "executable permissions, and specify the program using a pipe.<br><br>" + "Alternatively, in XMMS, go to Options->Preferences->Effects/General " + "Plugins.<br>Configure the \"Song Change\" plugin. In the song change" + " command box, put<br><br>echo \"%s\" > /path/to/output/file<br><br>" + "and be sure to enable the plugin. Select the file location in " + "AutoProfile and you should be done"), + } else if (!strcmp ((char *) data, "wmp")) { + purple_notify_formatted (NULL, _("Windows Media Player"), + _("Current song in Windows Media Player"), NULL, + _("Download NowPlaying, a plugin for WMP from <a href=\"" + "http://www.wmplugins.com/ItemDetail.aspx?ItemID=357\">" + "http://www.wmplugins.com/ItemDetail.aspx?ItemID=357</a> and follow " + "the included installation instructions.<br>Set the output filename " + "to the file you choose in this component"), + } else if (!strcmp ((char *) data, "amip")) { + purple_notify_formatted (NULL, _("iTunes/Winamp/Foobar/Apollo/QCD"), + _("Current song in iTunes/Winamp/Foobar/Apollo/QCD"), NULL, + _("Get the version of AMIP associated with your player from <a href=\"" + "http://amip.tools-for.net/\">" + "http://amip.tools-for.net/</a> and install/" + "Check the box \"Write song info to file\", play with the settings, " + "and set the file in this component to be the file in the AMIP " +GtkWidget *text_file_menu (struct widget *w) + GtkWidget *ret = gtk_vbox_new (FALSE, 5); + GtkWidget *hbox, *label, *button; + label = gtk_label_new (_("Select text file with source content")); + gtk_box_pack_start (GTK_BOX (ret), label, FALSE, FALSE, 0); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + hbox = gtk_hbox_new (FALSE, 5); + gtk_box_pack_start (GTK_BOX (ret), hbox, FALSE, FALSE, 0); + /* Text entry to type in file name */ + file_entry = gtk_entry_new (); + gtk_box_pack_start (GTK_BOX (hbox), file_entry, FALSE, FALSE, 0); + gtk_entry_set_text (GTK_ENTRY (file_entry), + ap_prefs_get_string (w, "text_file")); + g_signal_connect (G_OBJECT (file_entry), "focus-out-event", + G_CALLBACK (text_file_update), w); + /* Button to bring up file select dialog */ + button = gtk_button_new_with_label ("Browse ..."); + g_signal_connect (G_OBJECT (button), "clicked", + G_CALLBACK (text_file_selection), w); + gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0); + ap_prefs_labeled_spin_button (w, ret, + _("Max characters to read from file:"), "text_size", + 1, AP_SIZE_MAXIMUM - 1, NULL); + gtk_box_pack_start (GTK_BOX (ret), + gtk_hseparator_new (), 0, 0, 0); + label = gtk_label_new (_("Windows users: Play the current song in:")); + gtk_box_pack_start (GTK_BOX (ret), label, FALSE, FALSE, 0); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + hbox = gtk_hbox_new (FALSE, 5); + gtk_box_pack_start (GTK_BOX (ret), hbox, FALSE, FALSE, 0); + button = gtk_button_new_with_label ("iTunes/Winamp/Foobar/Apollo/QCD"); + g_signal_connect (G_OBJECT (button), "clicked", + G_CALLBACK (text_file_info_button), "amip"); + gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0); + button = gtk_button_new_with_label ("Windows Media Player"); + g_signal_connect (G_OBJECT (button), "clicked", + G_CALLBACK (text_file_info_button), "wmp"); + gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0); + hbox = gtk_hbox_new (FALSE, 5); + gtk_box_pack_start (GTK_BOX (ret), hbox, FALSE, FALSE, 0); + label = gtk_label_new (_("*nix users: Play the current song in:")); + gtk_box_pack_start (GTK_BOX (ret), label, FALSE, FALSE, 0); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + hbox = gtk_hbox_new (FALSE, 5); + gtk_box_pack_start (GTK_BOX (ret), hbox, FALSE, FALSE, 0); + button = gtk_button_new_with_label ("XMMS"); + g_signal_connect (G_OBJECT (button), "clicked", + G_CALLBACK (text_file_info_button), "xmms"); + gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0); + label = gtk_label_new (_("OS X users: Play the current song in:")); + gtk_box_pack_start (GTK_BOX (ret), label, FALSE, FALSE, 0); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + hbox = gtk_hbox_new (FALSE, 5); + gtk_box_pack_start (GTK_BOX (ret), hbox, FALSE, FALSE, 0); + button = gtk_button_new_with_label ("iTunes"); + g_signal_connect (G_OBJECT (button), "clicked", + G_CALLBACK (text_file_info_button), "itunes"); + gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0); +void text_file_init (struct widget *w) { + ap_prefs_add_string (w, "text_file", ""); + ap_prefs_add_int (w, "text_size", AP_SIZE_MAXIMUM - 1); + N_("Text File / Songs"), + N_("Copies text from file that external programs " + "(e.g. XMMS, Winamp, iTunes) can modify on a regular basis"), --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/autoprofile/comp_timestamp.c Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,142 @@
+/*--------------------------------------------------------------------------* + * A Purple away message and profile manager that supports dynamic text * + * AutoProfile is the legal property of its developers. 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 "../common/pp_internal.h" +/*---------- TIMESTAMP: Display time at creation ---------------------------*/ +static char *timestamp_generate (struct widget *w) { + time_t *general_time = (time_t *)malloc(sizeof(time_t)); + cur_time = ap_localtime (general_time); + ret = (char *)malloc (AP_SIZE_MAXIMUM); + strftime (ret, AP_SIZE_MAXIMUM - 1, + ap_prefs_get_string (w, "timestamp_format"), +static void timestamp_init (struct widget *w) { + ap_prefs_add_string (w, "timestamp_format", + "Automatically created at %I:%M %p"); +static GtkWidget *entry; +static void event_cb (GtkWidget *widget, struct widget *w) + ap_prefs_set_string (w, "timestamp_format", + gtk_imhtml_get_markup (GTK_IMHTML(entry))); +static void formatting_toggle_cb (GtkIMHtml *imhtml, + GtkIMHtmlButtons buttons, struct widget *w) + ap_prefs_set_string (w, "timestamp_format", + gtk_imhtml_get_markup (GTK_IMHTML(entry))); +static void formatting_clear_cb (GtkIMHtml *imhtml, + ap_prefs_set_string (w, "timestamp_format", + gtk_imhtml_get_markup (GTK_IMHTML(entry))); +static GtkWidget *timestamp_menu (struct widget *w) + GtkWidget *ret = gtk_vbox_new (FALSE, 5); + GtkWidget *entry_window, *toolbar; + GtkTextBuffer *text_buffer; + entry_window = pidgin_create_imhtml (TRUE, &entry, &toolbar, &sw); + gtk_box_pack_start (GTK_BOX (ret), entry_window, FALSE, FALSE, 0); gtk_imhtml_append_text_with_images (GTK_IMHTML(entry), + ap_prefs_get_string (w, "timestamp_format"), + text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (entry)); + g_signal_connect (G_OBJECT (text_buffer), "changed", + G_CALLBACK (event_cb), w); + g_signal_connect_after(G_OBJECT(entry), "format_function_toggle", + G_CALLBACK(formatting_toggle_cb), w); + g_signal_connect_after(G_OBJECT(entry), "format_function_clear", + G_CALLBACK(formatting_clear_cb), w); + label = gtk_label_new (_( + "Insert the following characters where time is to be displayed:\n\n" + "%H\thour (24-hour clock)\n" + "%I\thour (12-hour clock)\n" + "%a\tabbreviated weekday name\n" + "%A\tfull weekday name\n" + "%b\tabbreviated month name\n" + "%B\tfull month name\n" + "%m\tmonth (numerical)\n" + "%d\tday of the month\n" + "%j\tday of the year\n" + "%W\tweek number of the year\n" + "%w\tweekday (numerical)\n" + "%y\tyear without century\n" + "%Y\tyear with century\n" + "%z\ttime zone name, if any\n" + sw = gtk_scrolled_window_new (NULL,NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), + GTK_POLICY_NEVER, GTK_POLICY_ALWAYS); + gtk_box_pack_start (GTK_BOX (ret), sw, TRUE, TRUE , 0); + gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW(sw), label); +struct component timestamp = + N_("Displays custom text showing when message was created"), --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/autoprofile/comp_uptime.c Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,100 @@
+/*--------------------------------------------------------------------------* + * A Purple away message and profile manager that supports dynamic text * + * AutoProfile is the legal property of its developers. 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 "../common/pp_internal.h" +/*---------- UPTIME: Display the computer uptime --*/ +char *uptime_generate (struct widget *w) { + char *out, *line, *working; + char *p_character, *colon_character, *comma_character, *m_character; + exec = g_spawn_command_line_sync (line, + &out, NULL, NULL, &return_error); + /* Buffer length for safety */ + working = (char *)malloc (strlen (out)+7+8+8+1); + strcpy (working, "Uptime:"); + /* Break into minutes, hours, and everything else */ + p_character = strchr (out, 'p'); + m_character = strchr (p_character, 'm'); + /* Uptime format including "pm" */ + if (m_character != NULL && m_character == p_character + 1) { + p_character = strchr (m_character, 'p'); + m_character = strchr (p_character, 'm'); + /* Uptime if < 1 hour */ + if (m_character != NULL && *(m_character+1) == 'i') { + strcat (working, p_character); + strcat (working, "minutes"); + colon_character = strchr (p_character, ':'); + comma_character = strchr (colon_character, ','); + *colon_character++ = '\0'; + *comma_character = '\0'; + /* Yank it all together */ + strcat (working, p_character); + strcat (working, " hours, "); + strcat (working, colon_character); + strcat (working, " minutes"); + ap_debug ("uptime", "command failed to execute"); + return g_strdup(_("[ERROR: failed to execute uptime command]")); +struct component uptime = + N_("Show how long your computer has been running"), --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/autoprofile/component.c Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,87 @@
+/*--------------------------------------------------------------------------* + * A Purple away message and profile manager that supports dynamic text * + * AutoProfile is the legal property of its developers. 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 * + *--------------------------------------------------------------------------*/ +static GList *components = NULL; +static GList *get_components (); +void ap_component_start () { + if (components) g_list_free (components); + components = get_components (); +void ap_component_finish () { + g_list_free (components); +GList *ap_component_get_components () { +struct component *ap_component_get_component (const gchar *identifier) { + struct component *cur_comp; + for (comps = components; comps != NULL; comps = comps->next) { + cur_comp = (struct component *) comps->data; + if (!strcmp (cur_comp->identifier, identifier)) +static GList *get_components () + /* Add each listed component */ + ret = g_list_append (ret, &logstats); + ret = g_list_append (ret, &text); + ret = g_list_append (ret, "ation); + ret = g_list_append (ret, &rss); + ret = g_list_append (ret, ×tamp); + ret = g_list_append (ret, &http); + ret = g_list_append (ret, &count); + ret = g_list_append (ret, &executable); + ret = g_list_append (ret, &uptime); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/autoprofile/component.h Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,73 @@
+/*--------------------------------------------------------------------------* + * A Purple away message and profile manager that supports dynamic text * + * AutoProfile is the legal property of its developers. 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 _AP_COMPONENT_H_ +#define _AP_COMPONENT_H_ +/* A component is composed of code that generates some sort of content, + and a widget is a specific _instance_ of a component */ + char *(*generate)(struct widget *); + void (*init_pref)(struct widget *); + void (*load)(struct widget *); + void (*unload)(struct widget *); + gboolean (*has_content_changed)(struct widget *); + GtkWidget *(*pref_menu)(struct widget *); +void ap_component_start (); +void ap_component_finish (); +GList *ap_component_get_components (); +struct component *ap_component_get_component (const gchar *); +extern struct component logstats; +extern struct component count; +extern struct component executable; +extern struct component http; +extern struct component quotation; +extern struct component rss; +extern struct component text; +extern struct component timestamp; +extern struct component uptime; +#endif /* _AP_COMPONENT_H_ */ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/autoprofile/gtk_actions.c Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,341 @@
+/*--------------------------------------------------------------------------* + * A Purple away message and profile manager that supports dynamic text * + * AutoProfile is the legal property of its developers. 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 "autoprofile.h" +#include "gtkimhtmltoolbar.h" +static GtkWidget *current_profile = NULL; +static GtkWidget *accounts_dialog = NULL; +static GtkWidget *content_win = NULL; +/*--------------------------------------------------------------------------* + * Accounts edit popup window * + *--------------------------------------------------------------------------*/ +static void accounts_response_cb (GtkWidget *d, int response, gpointer data) + gtk_widget_destroy (accounts_dialog); + accounts_dialog = NULL; +static void display_accounts_dialog () { + if (accounts_dialog != NULL) { + gtk_window_present (GTK_WINDOW (accounts_dialog)); + accounts_dialog = gtk_dialog_new_with_buttons (_("Edit Profile Accounts"), + NULL, GTK_DIALOG_NO_SEPARATOR, NULL, NULL); + gtk_dialog_set_has_separator (GTK_DIALOG(accounts_dialog), TRUE); + gtk_dialog_add_button (GTK_DIALOG(accounts_dialog), GTK_STOCK_OK, + label = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL (label), + _("<b>No accounts currently enabled:</b> You have not yet specified\n " + "what accounts AutoProfile should set the profile for. Until you\n " + "check one of the boxes below, AutoProfile will effectively do\n " + gtk_box_pack_start (GTK_BOX(GTK_DIALOG(accounts_dialog)->vbox), label, + gtk_box_pack_start (GTK_BOX(GTK_DIALOG(accounts_dialog)->vbox), + get_account_page (), TRUE, TRUE, 0); + g_signal_connect (G_OBJECT(accounts_dialog), "response", + G_CALLBACK(accounts_response_cb), NULL); + gtk_window_set_default_size (GTK_WINDOW(accounts_dialog), 400, 450); + gtk_widget_show_all (accounts_dialog); +/*--------------------------------------------------------------------------* + * Profile edit window * + *--------------------------------------------------------------------------*/ +/* Callbacks for refreshing profile preview window */ +static void refresh_preview (GtkWidget *preview) { + // TODO: See if a delay timeout is necessary here + if (preview == NULL || current_profile == NULL) return; + gtk_imhtml_clear (GTK_IMHTML(preview)); + input = gtk_imhtml_get_markup ((GtkIMHtml *) current_profile); + output = ap_generate (input, AP_SIZE_PROFILE_MAX); + gtk_imhtml_append_text (GTK_IMHTML(preview), output, +static void refresh_cb (GtkWidget *widget, gpointer data) + refresh_preview ((GtkWidget *) data); +static void event_cb (GtkWidget *widget, gpointer data) + refresh_preview ((GtkWidget *) data); +static void formatting_toggle_cb (GtkIMHtml *imhtml, + GtkIMHtmlButtons buttons, gpointer data) + refresh_preview ((GtkWidget *) data); +static void formatting_clear_cb (GtkIMHtml *imhtml, gpointer data) + refresh_preview ((GtkWidget *) data); +static void revert_cb (GtkWidget *button, GtkWidget *imhtml) + gtk_imhtml_clear (GTK_IMHTML(imhtml)); + gtk_imhtml_append_text_with_images (GTK_IMHTML(imhtml), + purple_prefs_get_string ("/plugins/gtk/autoprofile/profile"), +static void save_cb (GtkWidget *button, GtkWidget *imhtml) + if (imhtml == NULL) return; + new_text = gtk_imhtml_get_markup ((GtkIMHtml *) imhtml); + purple_prefs_set_string ("/plugins/gtk/autoprofile/profile", new_text); + if (NULL == purple_prefs_get_string_list ( + "/plugins/gtk/autoprofile/profile_accounts")) { + // If no accounts set, ask for one! + display_accounts_dialog (); +/* Profile edit window */ +static GtkWidget *get_profile_page () + GtkWidget *hbox, *vbox, *dialog_box, *preview, *edit_window; + GtkWidget *label, *sw, *toolbar, *bbox; + GtkWidget *refresh_button, *revert_button, *save_button; + GtkTextBuffer *text_buffer; + ret = gtk_vbox_new (FALSE, 6); + dialog_box = gtk_vbox_new (FALSE, 4); + gtk_container_set_border_width (GTK_CONTAINER(dialog_box), 6); + gtk_box_pack_start (GTK_BOX(ret), dialog_box, TRUE, TRUE, 0); + hbox = gtk_hbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX(dialog_box), hbox, FALSE, FALSE, 0); + label = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL(label), _("<b>Preview</b>")); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + gtk_box_pack_start (GTK_BOX(hbox), label, FALSE, FALSE, 0); + refresh_button = gtk_button_new_with_label (_("Refresh")); + gtk_box_pack_end (GTK_BOX(hbox), refresh_button, FALSE, FALSE, 0); + sw = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(sw), + GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(sw), + gtk_box_pack_start (GTK_BOX(dialog_box), sw, TRUE, TRUE, 0); + preview = gtk_imhtml_new (NULL, NULL); + gtk_container_add (GTK_CONTAINER(sw), preview); + pidgin_setup_imhtml (preview); + gtk_imhtml_append_text (GTK_IMHTML(preview), + purple_prefs_get_string ("/plugins/gtk/autoprofile/profile"), + gtk_box_pack_start (GTK_BOX(ret), gtk_hseparator_new (), FALSE, FALSE, 0); + dialog_box = gtk_vbox_new (FALSE, 6); + gtk_container_set_border_width (GTK_CONTAINER(dialog_box), 6); + gtk_box_pack_start (GTK_BOX(ret), dialog_box, TRUE, TRUE, 0); + label = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL(label), + _("<b>Edit</b> (Drag widgets into profile / " + "Use shift+enter to insert a new line)")); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + gtk_box_pack_start (GTK_BOX(dialog_box), label, FALSE, FALSE, 0); + hbox = gtk_hbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX(dialog_box), hbox, TRUE, TRUE, 0); + vbox = gtk_vbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX(hbox), vbox, TRUE, TRUE, 0); + get_widget_list (vbox, &sel); + bbox = gtk_hbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX(vbox), bbox, FALSE, FALSE, 0); + revert_button = gtk_button_new_with_label (_("Revert")); + gtk_box_pack_start (GTK_BOX(bbox), revert_button, TRUE, TRUE, 0); + save_button = gtk_button_new_with_label (_("Save profile")); + gtk_box_pack_start (GTK_BOX(bbox), save_button, TRUE, TRUE, 0); + edit_window = pidgin_create_imhtml (TRUE, ¤t_profile, &toolbar, + gtk_box_pack_start (GTK_BOX(hbox), edit_window, TRUE, TRUE, 0); + g_signal_connect (G_OBJECT(save_button), "clicked", + G_CALLBACK(save_cb), current_profile); + g_signal_connect (G_OBJECT(revert_button), "clicked", + G_CALLBACK(revert_cb), current_profile); + g_signal_connect (G_OBJECT (refresh_button), "clicked", + G_CALLBACK (refresh_cb), preview); + text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (current_profile)); + g_signal_connect (G_OBJECT (text_buffer), "changed", + G_CALLBACK (event_cb), preview); + g_signal_connect_after(G_OBJECT(current_profile), "format_function_toggle", + G_CALLBACK(formatting_toggle_cb), preview); + g_signal_connect_after(G_OBJECT(current_profile), "format_function_clear", + G_CALLBACK(formatting_clear_cb), preview); + revert_cb (revert_button, current_profile); + refresh_cb (refresh_button, preview); +/*--------------------------------------------------------------------------* + * General edit window * + *--------------------------------------------------------------------------*/ +ap_edit_content_destroy (GtkWidget *button, GtkWidget *window) + gtk_widget_destroy (content_win); + done_with_widget_list (); + current_profile = NULL; +static void ap_edit_content_show () + gtk_window_present (GTK_WINDOW(content_win)); + /* Create the window */ + content_win = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_role (GTK_WINDOW(content_win), "ap_edit_content"); + gtk_window_set_title (GTK_WINDOW(content_win), _("Edit Content")); + gtk_window_set_default_size (GTK_WINDOW(content_win), 700, 550); + gtk_container_set_border_width (GTK_CONTAINER(content_win), 6); + g_signal_connect (G_OBJECT(content_win), "destroy", + G_CALLBACK(ap_edit_content_destroy), NULL); + vbox = gtk_vbox_new (FALSE, 6); + gtk_container_add (GTK_CONTAINER(content_win), vbox); + notebook = gtk_notebook_new (); + gtk_box_pack_start (GTK_BOX (vbox), notebook, TRUE, TRUE, 0); + gtk_notebook_append_page (GTK_NOTEBOOK(notebook), + ap_widget_get_config_page (), gtk_label_new (_("Widgets"))); + gtk_notebook_append_page (GTK_NOTEBOOK(notebook), get_profile_page (), + gtk_label_new (_("Info/profile"))); + /* The buttons to press! */ + bbox = gtk_hbutton_box_new (); + gtk_box_set_spacing (GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE); + gtk_button_box_set_layout (GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END); + gtk_box_pack_start (GTK_BOX(vbox), bbox, FALSE, FALSE, 0); + button = gtk_button_new_from_stock (GTK_STOCK_CLOSE); + g_signal_connect (G_OBJECT(button), "clicked", + G_CALLBACK(ap_edit_content_destroy), NULL); + gtk_box_pack_start (GTK_BOX(bbox), button, FALSE, FALSE, 0); + gtk_widget_show_all (content_win); +/*--------------------------------------------------------------------------* + * The actions themselves * + *--------------------------------------------------------------------------*/ +static void edit_content (PurplePluginAction *action) + ap_edit_content_show (); +static void edit_preferences (PurplePluginAction *action) + ap_preferences_display (); +static void make_visible (PurplePluginAction *action) + ap_gtk_make_visible (); +/* Return the actions */ +GList *actions (PurplePlugin *plugin, gpointer context) + PurplePluginAction *act; + act = purple_plugin_action_new (_("Edit Content"), edit_content); + list = g_list_append (list, act); + act = purple_plugin_action_new (_("Preferences"), edit_preferences); + list = g_list_append (list, act); + act = purple_plugin_action_new (_("Show summary"), make_visible); + list = g_list_append (list, act); +void ap_actions_finish () + if (content_win) ap_edit_content_destroy (NULL, NULL); + if (accounts_dialog) accounts_response_cb (NULL, 0, NULL); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/autoprofile/gtk_away_msgs.c Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,486 @@
+/*--------------------------------------------------------------------------* + * A Purple away message and profile manager that supports dynamic text * + * AutoProfile is the legal property of its developers. 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 "autoprofile.h" +/* VARIABLE DEFINITIONS */ +static guint queue_pref_cb = 0; +static guint sound_pref_cb = 0; +static gboolean ap_previously_away = FALSE; +/* The list containing data on generated profiles / status messages */ +static GtkListStore *message_list = NULL; +/* The general window */ +static GtkWidget *dialog = NULL; +typedef struct _ap_progress_bar { +static GHashTable *progress_bars = NULL; +/*--------------------------------------------------------------------------* + *--------------------------------------------------------------------------*/ +static void hide_cb (GtkButton *button, gpointer data) { + gtk_widget_hide_all (dialog); + const char *name, PurplePrefType type, gconstpointer val, gpointer data) + const char *name, PurplePrefType type, gconstpointer val, gpointer data) + button = (GtkWidget *) data; + value = purple_prefs_get_bool ("/core/sound/while_away"); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(button), value); +static void update_summary_visibility () + const gchar *summary_pref; + // Decide whether or not to show window + summary_pref = purple_prefs_get_string ( + "/plugins/gtk/autoprofile/show_summary"); + if (!strcmp (summary_pref, "always")) { + gtk_widget_show_all (dialog); + } else if (!strcmp (summary_pref, "away") && ap_is_currently_away ()) { + gtk_widget_show_all (dialog); + gtk_widget_hide_all (dialog); + ap_previously_away = ap_is_currently_away (); +/*--------------------------------------------------------------------------* + * Displayed message stuff * + *--------------------------------------------------------------------------*/ +static void display_diff_msg (GtkTreeSelection *select, gpointer data) + GtkWidget *imhtml = (GtkWidget *) data; + if (gtk_tree_selection_get_selected (select, &model, &iter)) + gtk_tree_model_get (model, &iter, 3, &string, -1); + gtk_imhtml_clear (GTK_IMHTML(imhtml)); + gtk_imhtml_append_text (GTK_IMHTML(imhtml), string, + gtk_imhtml_append_text (GTK_IMHTML(imhtml), "<BR>", +/*--------------------------------------------------------------------------* + *--------------------------------------------------------------------------*/ +static APProgressBar *progress_create (APUpdateType type, + APProgressBar *progress_bar; + progress_bar = (APProgressBar *) malloc (sizeof (APProgressBar)); + progress_bar->timeout = 0; + progress_bar->type = type; + progress_bar->bar = gtk_progress_bar_new (); + gtk_progress_bar_set_bar_style (GTK_PROGRESS_BAR(progress_bar->bar), GTK_PROGRESS_CONTINUOUS); + gtk_box_pack_start (GTK_BOX(container), progress_bar->bar, FALSE, FALSE, 0); + if (type == AP_UPDATE_PROFILE) { + gtk_progress_bar_set_text (GTK_PROGRESS_BAR (progress_bar->bar), + _("no updates made to profile")); + } else if (type == AP_UPDATE_STATUS) { + gtk_progress_bar_set_text (GTK_PROGRESS_BAR (progress_bar->bar), + _("no updates made to status")); + g_hash_table_insert (progress_bars, GINT_TO_POINTER(type), progress_bar); +static void progress_update_stop (APProgressBar *progress_bar) { + if (progress_bar->timeout) { + purple_timeout_remove (progress_bar->timeout); + progress_bar->timeout = 0; + gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR(progress_bar->bar), 1.0); + if (progress_bar->type == AP_UPDATE_PROFILE) { + gtk_progress_bar_set_text (GTK_PROGRESS_BAR (progress_bar->bar), + _("waiting for new profile content")); + } else if (progress_bar->type == AP_UPDATE_STATUS) { + gtk_progress_bar_set_text (GTK_PROGRESS_BAR (progress_bar->bar), + _("waiting for new status content")); +static gboolean progress_update (gpointer data) { + int total_milliseconds; + double fraction_increment; + APProgressBar *progress_bar = (APProgressBar *) data; + // Update fraction on bar + purple_prefs_get_int ("/plugins/gtk/autoprofile/delay_update") * 1000; + fraction_increment = BAH / ((double) total_milliseconds); + cur_fraction = gtk_progress_bar_get_fraction ( + GTK_PROGRESS_BAR(progress_bar->bar)); + result = cur_fraction + fraction_increment; + progress_update_stop (progress_bar); + gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR(progress_bar->bar), result); + seconds_remaining = (int) + (((double) total_milliseconds / 1000) - + (cur_fraction * (double) total_milliseconds / 1000)); + text = g_string_new (""); + if (progress_bar->type == AP_UPDATE_PROFILE) { + g_string_printf (text, _("next profile update in %d seconds"), + } else if (progress_bar->type == AP_UPDATE_STATUS) { + g_string_printf (text, _("next status update in %d seconds"), + gtk_progress_bar_set_text (GTK_PROGRESS_BAR (progress_bar->bar), text->str); + g_string_free (text, TRUE); +static void ap_gtk_timeout_start (APUpdateType type) { + APProgressBar *progress_bar; + progress_bar = g_hash_table_lookup (progress_bars, GINT_TO_POINTER(type)); + if (progress_bar->timeout) { + purple_timeout_remove (progress_bar->timeout); + gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (progress_bar->bar), 0); + progress_bar->timeout = + purple_timeout_add (BAH, progress_update, progress_bar); + progress_update (progress_bar); +void ap_gtk_set_progress_visible (APUpdateType type, gboolean visible) + APProgressBar *progress_bar; + progress_bar = g_hash_table_lookup (progress_bars, GINT_TO_POINTER(type)); + if (visible) gtk_widget_show (progress_bar->bar); + else gtk_widget_hide (progress_bar->bar); +/*--------------------------------------------------------------------------* + * Create the main window * + *--------------------------------------------------------------------------*/ +static void create_dialog () { + GtkTreeViewColumn *column; + GtkCellRenderer *renderer; + GtkTreeSelection *selection; + GtkWidget *message_list_view; + GtkWidget *vbox, *vpane, *hbox, *config_vbox; + GtkWidget *sw, *imhtml, *msg_window, *button; + imhtml = gtk_imhtml_new (NULL, NULL); + /* Create main display window */ + gtk_window_set_title (GTK_WINDOW(dialog), _("AutoProfile Summary")); + gtk_widget_realize (dialog); + vbox = gtk_vbox_new (FALSE, 5); + gtk_container_add (GTK_CONTAINER (dialog), vbox); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 5); + /* Set up progress bar container */ + progress_create (AP_UPDATE_PROFILE, vbox); + progress_create (AP_UPDATE_STATUS, vbox); + /* Set up list of past away messages */ + vpane = gtk_vpaned_new (); + gtk_box_pack_start (GTK_BOX(vbox), vpane, TRUE, TRUE, 0); + message_list = gtk_list_store_new (4, + G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING); + message_list_view = gtk_tree_view_new_with_model ( + GTK_TREE_MODEL (message_list)); + renderer = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes ( + _("Time"), renderer, "markup", 0, NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (message_list_view), column); + gtk_tree_view_column_set_sort_column_id (column, 0); + column = gtk_tree_view_column_new_with_attributes ( + _("Type"), renderer, "markup", 1, NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (message_list_view), column); + gtk_tree_view_column_set_sort_column_id (column, 1); + renderer = gtk_cell_renderer_text_new (); + g_object_set (renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL); + column = gtk_tree_view_column_new_with_attributes ( + _("Text"), renderer, "markup", 2, NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (message_list_view), column); + gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED); + sw = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(sw), + GTK_POLICY_NEVER, GTK_POLICY_ALWAYS); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(sw), + gtk_container_add (GTK_CONTAINER (sw), message_list_view); + gtk_paned_add1 (GTK_PANED(vpane), sw); + selection = gtk_tree_view_get_selection ( + GTK_TREE_VIEW (message_list_view)); + gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE); + g_signal_connect (G_OBJECT (selection), "changed", + G_CALLBACK (display_diff_msg), imhtml); + /* Set up the window to display away message in */ + msg_window = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(msg_window), + GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(msg_window), + gtk_paned_add2 (GTK_PANED(vpane), msg_window); + gtk_container_add (GTK_CONTAINER(msg_window), imhtml); + pidgin_setup_imhtml (imhtml); + hbox = gtk_hbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + config_vbox = gtk_vbox_new (FALSE, 4); + gtk_box_pack_start (GTK_BOX(hbox), config_vbox, TRUE, TRUE, 0); + pidgin_prefs_checkbox ( + _("Queue new messages while away"), + "/plugins/gtk/autoprofile/queue_messages_when_away", + button = pidgin_prefs_checkbox ( + _("Play sounds while away"), + "/core/sound/while_away", + sound_pref_cb = purple_prefs_connect_callback (ap_get_plugin_handle (), + "/core/sound/while_away", sound_cb, button); + gtk_box_pack_start (GTK_BOX(hbox), gtk_vseparator_new (), FALSE, FALSE, 0); + config_vbox = gtk_vbox_new (FALSE, 4); + gtk_box_pack_start (GTK_BOX(hbox), config_vbox, TRUE, TRUE, 0); + ap_gtk_prefs_add_summary_option (config_vbox); + button = gtk_button_new_with_label (_("Hide summary now")); + gtk_box_pack_start (GTK_BOX(config_vbox), button, FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (hide_cb), NULL); + g_signal_connect (G_OBJECT(dialog), "delete-event", + G_CALLBACK(gtk_widget_hide_on_delete), NULL); + gtk_paned_set_position (GTK_PANED(vpane), 250); + gtk_window_set_default_size (GTK_WINDOW(dialog), 430, 430); +/*--------------------------------------------------------------------------* + *--------------------------------------------------------------------------*/ +void ap_gtk_add_message (APUpdateType update_type, APMessageType type, + char *time_string, *simple_text, *s; + // Create the time string + general_time = (time_t *) malloc (sizeof(time_t)); + cur_time = ap_localtime (general_time); + time_string = (char *) malloc (sizeof(char[32])); + strftime (time_string, 31, "<b>%I:%M %p</b>", cur_time); + // Create the type string + type_string = strdup("<b>Status</b>"); + case AP_MESSAGE_TYPE_PROFILE: + type_string = strdup (_("<b>User profile</b>")); + case AP_MESSAGE_TYPE_AWAY: + type_string = strdup (_("<b>Away message</b>")); + case AP_MESSAGE_TYPE_AVAILABLE: + type_string = strdup (_("<b>Available message</b>")); + case AP_MESSAGE_TYPE_STATUS: + type_string = strdup (_("<b>Status message</b>")); + type_string = strdup (_("<b>Other</b>")); + simple_text = strdup (text); + // Only show the first line + s = (gchar *) purple_strcasestr (simple_text, "<br>"); + simple_text = purple_markup_strip_html (simple_text); + gtk_list_store_prepend (message_list, &iter); + gtk_list_store_set (message_list, &iter, + if (simple_text) free (simple_text); + if (gtk_tree_model_iter_nth_child + (GTK_TREE_MODEL(message_list), &iter, NULL, + AP_GTK_MAX_MESSAGES)) { + gtk_list_store_remove (message_list, &iter); + // Move the timeout bar + ap_gtk_timeout_start (update_type); + // Check if it needs to be visible or not + if (type != AP_MESSAGE_TYPE_PROFILE && + ap_is_currently_away () != ap_previously_away) { + update_summary_visibility (); +void ap_gtk_make_visible () + gtk_widget_show_all (dialog); + gtk_window_present (GTK_WINDOW(dialog)); + progress_bars = g_hash_table_new (NULL, NULL); + queue_pref_cb = purple_prefs_connect_callback ( + ap_get_plugin_handle (), + "/plugins/gtk/autoprofile/queue_messages_when_away", queue_cb, NULL); + update_summary_visibility (); +static void ap_gtk_finish_progress_bar (APUpdateType type) + APProgressBar *progress_bar; + progress_bar = g_hash_table_lookup (progress_bars, GINT_TO_POINTER(type)); + if (progress_bar->timeout) { + purple_timeout_remove (progress_bar->timeout); + g_hash_table_insert (progress_bars, GINT_TO_POINTER(type), NULL); + // Kill the window and associated variables + gtk_widget_destroy (dialog); + ap_gtk_finish_progress_bar (AP_UPDATE_PROFILE); + ap_gtk_finish_progress_bar (AP_UPDATE_STATUS); + // Disconnect queue message + purple_prefs_disconnect_callback (queue_pref_cb); + purple_prefs_disconnect_callback (sound_pref_cb); + g_hash_table_destroy (progress_bars); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/autoprofile/gtk_widget.c Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,778 @@
+/*--------------------------------------------------------------------------* + * A Purple away message and profile manager that supports dynamic text * + * AutoProfile is the legal property of its developers. 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 "autoprofile.h" +#define AP_RESPONSE_CHOOSE 98125 +static GtkWidget *dialog_box = NULL; +static GtkWidget *dialog_box_contents = NULL; +static GtkWidget *dialog_box_preview = NULL; +static struct widget *dialog_box_widget = NULL; +static GStaticMutex preview_mutex = G_STATIC_MUTEX_INIT; +static GtkWidget *component_dialog = NULL; +static GtkWidget *choose_button = NULL; +static GtkWidget *widget_dialog = NULL; +static GtkWidget *delete_button = NULL; +static GtkWidget *rename_button = NULL; +static GtkListStore *tree_list = NULL; +static GHashTable *pref_names = NULL; +static void component_dialog_show (); +void ap_widget_prefs_updated (struct widget *w) { + if (dialog_box_preview == NULL) return; + if (w != dialog_box_widget) return; + // TODO: Investigate how laggy this is, possibly add a timeout + output = w->component->generate (w); + g_static_mutex_lock (&preview_mutex); + gtk_imhtml_clear (GTK_IMHTML(dialog_box_preview)); + gtk_imhtml_append_text (GTK_IMHTML(dialog_box_preview), output, + g_static_mutex_unlock (&preview_mutex); +static void update_widget_list (GtkListStore *ls) { + GList *widgets, *widgets_start; + gtk_list_store_clear (ls); + widgets_start = widgets = ap_widget_get_widgets (); + for (widgets = widgets_start; widgets != NULL; widgets = widgets->next) { + gtk_list_store_append (ls, &iter); + w = (struct widget *) widgets->data; + g_string_printf (s, "<b>%s</b>", w->alias); + gtk_list_store_set (ls, &iter, + g_list_free (widgets_start); + g_string_free (s, TRUE); +static void refresh_cb (GtkWidget *widget, gpointer data) { + w = (struct widget *) data; + ap_widget_prefs_updated (w); +/* Widget configuration menu */ +static GtkWidget *get_widget_configuration (struct widget *w) { + GtkWidget *config, *hbox, *vbox, *sw; + GtkWidget *label, *button; + config = gtk_vbox_new (FALSE, 0); + /* Title/Description */ + hbox = gtk_hbox_new (FALSE, 8); + gtk_container_set_border_width (GTK_CONTAINER(hbox), 6); + gtk_box_pack_start (GTK_BOX(config), hbox, FALSE, FALSE, 0); + g_string_printf (s, "<b>%s:</b> %s", w->component->name, + w->component->description); + label = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL(label), s->str); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + g_string_free (s, TRUE); + gtk_box_pack_start (GTK_BOX (config), gtk_hseparator_new (), + vbox = gtk_vbox_new (FALSE, 6); + gtk_container_set_border_width (GTK_CONTAINER(vbox), 6); + gtk_box_pack_start (GTK_BOX(config), vbox, FALSE, FALSE, 0); + hbox = gtk_hbox_new (FALSE, 8); + gtk_box_pack_start (GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + label = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL(label), _("<b>Preview</b>")); + gtk_box_pack_start (GTK_BOX(hbox), label, FALSE, FALSE, 0); + button = gtk_button_new_with_label (_("Refresh")); + gtk_box_pack_end (GTK_BOX(hbox), button, FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (button), "clicked", + G_CALLBACK (refresh_cb), w); + sw = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(sw), + GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(sw), + gtk_box_pack_start (GTK_BOX(vbox), sw, TRUE, TRUE, 0); + dialog_box_preview = gtk_imhtml_new (NULL, NULL); + gtk_container_add (GTK_CONTAINER(sw), dialog_box_preview); + pidgin_setup_imhtml (dialog_box_preview); + output = w->component->generate (w); + gtk_imhtml_append_text (GTK_IMHTML(dialog_box_preview), + output, GTK_IMHTML_NO_SCROLL); + gtk_box_pack_start (GTK_BOX (config), gtk_hseparator_new (), + /* Configuration stuff */ + vbox = gtk_vbox_new (FALSE, 8); + gtk_container_set_border_width (GTK_CONTAINER(vbox), 6); + gtk_box_pack_start (GTK_BOX(config), vbox, TRUE, TRUE, 0); + label = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL(label), _("<b>Configuration</b>")); + gtk_box_pack_start (GTK_BOX(vbox), label, FALSE, FALSE, 0); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + if (w->component->pref_menu == NULL || + (menu = (w->component->pref_menu) (w)) == NULL) { + label = gtk_label_new (_("No options available for this component")); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (vbox), menu, TRUE, TRUE, 0); +static GtkWidget *get_info_message () { + page = gtk_vbox_new (FALSE, 8); + gtk_container_set_border_width (GTK_CONTAINER (page), 12); + aboutwin = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(aboutwin), + GTK_POLICY_NEVER, GTK_POLICY_ALWAYS); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(aboutwin), + gtk_box_pack_start (GTK_BOX(page), aboutwin, TRUE, TRUE, 0); + text = gtk_imhtml_new (NULL, NULL); + gtk_container_add (GTK_CONTAINER(aboutwin), text); + pidgin_setup_imhtml (text); + gtk_imhtml_append_text (GTK_IMHTML(text), + _("<b><u>Basic info</u></b><br>"), GTK_IMHTML_NO_SCROLL); + gtk_imhtml_append_text (GTK_IMHTML(text), + _("A <b>widget</b> is a little piece/snippet of automatically " + "generated text. There are all sorts of widgets; each type has " + "different content (i.e. a random quote, text from a blog, the " + "song currently playing, etc).<br><br>"), GTK_IMHTML_NO_SCROLL); + gtk_imhtml_append_text (GTK_IMHTML(text), + _("To use a widget, simply drag it from the list on the left and " + "drop it into a profile or status message. <i>It's that easy!</i>" + "<br><br>"), GTK_IMHTML_NO_SCROLL); + gtk_imhtml_append_text (GTK_IMHTML(text), + _("<b>To edit your profile:</b> " + "Use the \"Info/profile\" tab in this window.<br>"), + gtk_imhtml_append_text (GTK_IMHTML(text), + _("<b>To edit your available/away/status message:</b> " + "Use the regular Purple interface built into the bottom of the buddy " + "list.<br><br>"), GTK_IMHTML_NO_SCROLL); + gtk_imhtml_append_text (GTK_IMHTML(text), + _("<b><u>Advanced Tips</u></b><br>"), GTK_IMHTML_NO_SCROLL); + gtk_imhtml_append_text (GTK_IMHTML(text), + _("You can insert a widget into a profile or status by typing its name. " + "To do this, just type \"[widget-name]\" wherever you want to " + "place a widget (names of widgets are listed on the left). <br><br>" + "<b>You type:</b> The song I am playing now is [iTunesInfo].<br>" + "<b>AutoProfile result:</b> The song I am playing now is " + "The Beatles - Yellow Submarine.<br><br>"), GTK_IMHTML_NO_SCROLL); +/* Dialog window actions */ +static void widget_popup_rename_cb ( + struct widget *w, const char *new_text) { + struct widget *cur_widget; + gtk_tree_model_get_iter_first (GTK_TREE_MODEL(tree_list), &iter); + gtk_tree_model_get_value (GTK_TREE_MODEL(tree_list), &iter, 1, &val); + cur_widget = g_value_get_pointer(&val); + if (cur_widget == w) break; + if (!gtk_tree_model_iter_next (GTK_TREE_MODEL(tree_list), &iter)) { + purple_notify_error (NULL, NULL, + N_("Unable to change name"), + N_("The specified widget no longer exists.")); + if (ap_widget_rename (w, new_text)) { + g_string_printf (s, "<b>%s</b>", w->alias); + gtk_list_store_set (tree_list, &iter, + g_string_free (s, TRUE); + purple_notify_error (NULL, NULL, + N_("Unable to change name"), + N_("The widget name you have specified is already in use.")); +static void delete_cb (GtkWidget *button, GtkTreeSelection *sel) + gtk_tree_selection_get_selected (sel, &model, &iter); + gtk_tree_model_get_value (model, &iter, 1, &val); + w = g_value_get_pointer(&val); + gtk_list_store_remove (GTK_LIST_STORE(model), &iter); +static void rename_cb (GtkWidget *button, GtkTreeSelection *sel) + gtk_tree_selection_get_selected (sel, &model, &iter); + gtk_tree_model_get_value (model, &iter, 1, &val); + w = g_value_get_pointer(&val); + purple_request_input(NULL, + _("Rename Widget"), NULL, + _("Enter a new name for this widget."), w->alias, + _("Rename"), G_CALLBACK(widget_popup_rename_cb), + _("Cancel"), NULL, NULL, NULL, NULL, w); +static void add_cb (GtkWidget *button, GtkTreeSelection *sel) + component_dialog_show (); +void ap_widget_gtk_start () { + pref_names = g_hash_table_new (g_str_hash, g_str_equal); +void ap_widget_gtk_finish () { + done_with_widget_list (); + g_hash_table_destroy (pref_names); +static void widget_sel_cb (GtkTreeSelection *sel, GtkTreeModel *model) { + gtk_widget_destroy (dialog_box_contents); + if (!gtk_tree_selection_get_selected (sel, &model, &iter)) { + gtk_widget_set_sensitive(rename_button, FALSE); + gtk_widget_set_sensitive(delete_button, FALSE); + dialog_box_contents = get_info_message (); + dialog_box_preview = NULL; + dialog_box_widget = NULL; + gtk_widget_set_sensitive(rename_button, TRUE); + gtk_widget_set_sensitive(delete_button, TRUE); + gtk_tree_model_get_value (GTK_TREE_MODEL(tree_list), &iter, 1, &val); + w = g_value_get_pointer(&val); + dialog_box_contents = get_widget_configuration (w); + gtk_box_pack_start (GTK_BOX(dialog_box), dialog_box_contents, + gtk_widget_show_all (dialog_box); +GtkWidget *ap_widget_get_config_page () + /* Arrange main parts of window */ + dialog_box = gtk_hbox_new (FALSE, 0); + vbox = gtk_vbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX(dialog_box), vbox, FALSE, FALSE, 0); + get_widget_list (vbox, &sel); + g_signal_connect (G_OBJECT (sel), "changed", G_CALLBACK (widget_sel_cb), + add_button = gtk_button_new_with_label (_("New Widget")); + g_signal_connect (G_OBJECT(add_button), "clicked", + G_CALLBACK(add_cb), sel); + gtk_box_pack_start (GTK_BOX(vbox), add_button, FALSE, FALSE, 0); + rename_button = gtk_button_new_with_label (_("Rename")); + gtk_widget_set_sensitive(rename_button, FALSE); + g_signal_connect (G_OBJECT(rename_button), "clicked", + G_CALLBACK(rename_cb), sel); + gtk_box_pack_start (GTK_BOX(vbox), rename_button, FALSE, FALSE, 0); + delete_button = gtk_button_new_with_label (_("Delete")); + gtk_widget_set_sensitive(delete_button, FALSE); + g_signal_connect (G_OBJECT(delete_button), "clicked", + G_CALLBACK(delete_cb), sel); + gtk_box_pack_start (GTK_BOX(vbox), delete_button, FALSE, FALSE, 0); + dialog_box_contents = get_info_message (); + gtk_box_pack_start (GTK_BOX(dialog_box), dialog_box_contents, +drag_data_get_cb(GtkWidget *widget, GdkDragContext *ctx, + GtkSelectionData *data, guint info, guint time, + GtkListStore *ls = (GtkListStore *) user_data; + if (ls == NULL) return; + if (data->target == gdk_atom_intern ("STRING", FALSE)) { + GtkTreeRowReference *ref; + GtkTreePath *source_row; + ref = g_object_get_data (G_OBJECT(ctx), "gtk-tree-view-source-row"); + source_row = gtk_tree_row_reference_get_path (ref); + if (source_row == NULL) return; + gtk_tree_model_get_iter(GTK_TREE_MODEL(ls), &iter, source_row); + gtk_tree_model_get_value(GTK_TREE_MODEL(ls), &iter, + w = g_value_get_pointer (&val); + g_string_printf (s, "[%s]", w->alias); + gtk_selection_data_set (data, gdk_atom_intern ("STRING", FALSE), + 8, s->str, strlen(s->str)+1); + g_string_free (s, TRUE); + gtk_tree_path_free (source_row); +void done_with_widget_list () { + g_object_unref (tree_list); + dialog_box_contents = NULL; + dialog_box_preview = NULL; + dialog_box_widget = NULL; + if (component_dialog != NULL) { + gtk_widget_destroy (component_dialog); + component_dialog = NULL; +GtkWidget *get_widget_list (GtkWidget *box, GtkTreeSelection **sel) + GtkTreeViewColumn *col; + GtkTargetEntry gte [] = {{"STRING", 0, GTK_IMHTML_DRAG_STRING}}; + if (tree_list == NULL) { + tree_list = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_POINTER); + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(tree_list), + 0, GTK_SORT_ASCENDING); + update_widget_list (tree_list); + g_object_ref (G_OBJECT(tree_list)); + sw = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(sw), + GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(sw), + gtk_box_pack_start (GTK_BOX(box), sw, TRUE, TRUE, 0); + event_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(tree_list)); + *sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (event_view)); + rend = gtk_cell_renderer_text_new (); + col = gtk_tree_view_column_new_with_attributes (_("Widget"), rend, + gtk_tree_view_append_column (GTK_TREE_VIEW (event_view), col); + gtk_tree_view_column_set_sort_column_id (col, 0); + gtk_container_add (GTK_CONTAINER(sw), event_view); + gtk_tree_view_enable_model_drag_source( + GTK_TREE_VIEW(event_view), GDK_BUTTON1_MASK, gte, + g_signal_connect(G_OBJECT(event_view), "drag-data-get", + G_CALLBACK(drag_data_get_cb), tree_list); +/********************************************************* + Component selection window +**********************************************************/ +static void add_component (struct component *c) { + w = ap_widget_create (c); + gtk_list_store_append (tree_list, &iter); + g_string_printf (s, "<b>%s</b>", w->alias); + gtk_list_store_set (tree_list, &iter, + g_string_free (s, TRUE); +static void component_row_activate_cb (GtkTreeView *view, GtkTreePath *path, + GtkTreeViewColumn *column, gpointer null) + sel = gtk_tree_view_get_selection (view); + if (gtk_tree_selection_get_selected (sel, &model, &iter)) { + gtk_tree_model_get (model, &iter, 1, &c, -1); + gtk_widget_destroy (component_dialog); + component_dialog = NULL; +static void component_response_cb(GtkWidget *d, int response, + case AP_RESPONSE_CHOOSE: + gtk_tree_selection_get_selected (sel, &model, &iter); + gtk_tree_model_get_value (model, &iter, 1, &val); + c = g_value_get_pointer(&val); + case GTK_RESPONSE_CLOSE: + case GTK_RESPONSE_CANCEL: + case GTK_RESPONSE_DELETE_EVENT: + component_dialog = NULL; +static void component_sel_cb (GtkTreeSelection *sel, GtkTreeModel *model) { + if (!gtk_tree_selection_get_selected (sel, &model, &iter)) { + gtk_widget_set_sensitive(choose_button, FALSE); + gtk_widget_set_sensitive(choose_button, TRUE); +static void update_component_list (GtkListStore *ls) { + gchar *name, *description; + gtk_list_store_clear (ls); + for (components = ap_component_get_components (); + components = components->next) { + gtk_list_store_append (ls, &iter); + c = (struct component *) components->data; + name = g_markup_escape_text (c->name, -1); + description = g_markup_escape_text (c->description, -1); + g_string_printf (s, "<b>%s</b>\n%s", name, description); + gtk_list_store_set (ls, &iter, + g_string_free (s, TRUE); +static void component_dialog_show () + GtkCellRenderer *rendt; + GtkTreeViewColumn *col; + if (component_dialog != NULL) { + gtk_window_present(GTK_WINDOW(component_dialog)); + component_dialog = gtk_dialog_new_with_buttons (_("Select a widget type"), + GTK_DIALOG_NO_SEPARATOR, + choose_button = gtk_dialog_add_button (GTK_DIALOG(component_dialog), + _("Create widget"), AP_RESPONSE_CHOOSE); + gtk_dialog_add_button (GTK_DIALOG(component_dialog), + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL); + gtk_widget_set_sensitive (choose_button, FALSE); + sw = gtk_scrolled_window_new (NULL,NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(sw), + GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(sw), + gtk_box_pack_start (GTK_BOX(GTK_DIALOG(component_dialog)->vbox), sw, + ls = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_POINTER); + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(ls), + 0, GTK_SORT_ASCENDING); + update_component_list (ls); + event_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL(ls)); + g_signal_connect(G_OBJECT(event_view), "row-activated", + G_CALLBACK(component_row_activate_cb), event_view); + sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (event_view)); + rendt = gtk_cell_renderer_text_new (); + col = gtk_tree_view_column_new_with_attributes (_("Widget type"), +#if GTK_CHECK_VERSION(2,6,0) + gtk_tree_view_column_set_expand (col, TRUE); + g_object_set(rendt, "ellipsize", PANGO_ELLIPSIZE_END, NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW(event_view), col); + gtk_tree_view_column_set_sort_column_id (col, 0); + g_object_unref (G_OBJECT(ls)); + gtk_container_add (GTK_CONTAINER(sw), event_view); + g_signal_connect (G_OBJECT (sel), "changed", + G_CALLBACK (component_sel_cb), NULL); + g_signal_connect(G_OBJECT(component_dialog), "response", + G_CALLBACK(component_response_cb), sel); + gtk_window_set_default_size(GTK_WINDOW(component_dialog), 550, 430); + gtk_widget_show_all(component_dialog); +static void pref_callback (const char *name, PurplePrefType type, + gconstpointer val, gpointer data) { + struct widget *w = (struct widget *) data; + ap_widget_prefs_updated (w); +static const gchar *get_const_pref (struct widget *w, const char *key) { + // This is here to prevent memory leaks + pref = ap_prefs_get_pref_name (w, key); + if (pref_names == NULL) { + pref_names = g_hash_table_new (g_str_hash, g_str_equal); + result = g_hash_table_lookup (pref_names, pref); + g_hash_table_insert (pref_names, pref, pref); +GtkWidget *ap_prefs_checkbox (struct widget *w, const char *title, + const char *key, GtkWidget *page) + pref = get_const_pref (w, key); + result = pidgin_prefs_checkbox (title, pref, page); + purple_prefs_connect_callback (ap_get_plugin_handle (), pref, +GtkWidget *ap_prefs_dropdown_from_list (struct widget *w, GtkWidget *page, + const gchar *title, PurplePrefType type, const char *key, GList *menuitems) + pref = get_const_pref (w, key); + result = pidgin_prefs_dropdown_from_list ( + page, title, type, pref, menuitems); + purple_prefs_connect_callback (ap_get_plugin_handle (), pref, +GtkWidget *ap_prefs_labeled_entry (struct widget *w, GtkWidget *page, + const gchar *title, const char *key, GtkSizeGroup *sg) { + pref = get_const_pref (w, key); + result = pidgin_prefs_labeled_entry (page, title, pref, sg); + purple_prefs_connect_callback (ap_get_plugin_handle (), pref, +GtkWidget *ap_prefs_labeled_spin_button (struct widget *w, + GtkWidget *page, const gchar *title, const char *key, int min, + int max, GtkSizeGroup *sg) + pref = get_const_pref (w, key); + result = pidgin_prefs_labeled_spin_button (page, title, pref, + purple_prefs_connect_callback (ap_get_plugin_handle (), pref, --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/autoprofile/preferences.c Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,750 @@
+/*--------------------------------------------------------------------------* + * A Purple away message and profile manager that supports dynamic text * + * AutoProfile is the legal property of its developers. 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 "autoprofile.h" +#include "gtksavedstatuses.h" +/*--------------------------------------------------------------------------* + *--------------------------------------------------------------------------*/ +static GtkWidget *get_info_page () { + gchar *labeltext, *str; + page = gtk_vbox_new (FALSE, 5); + gtk_container_set_border_width (GTK_CONTAINER (page), 5); + /* AutoProfile title */ + labeltext = g_strdup_printf ( + _("<span weight=\"bold\" size=\"larger\">AutoProfile %s</span>"), + label = gtk_label_new (NULL); + gtk_label_set_markup (GTK_LABEL(label), labeltext); + gtk_label_set_line_wrap (GTK_LABEL(label), TRUE); + gtk_misc_set_alignment (GTK_MISC(label), 0.5, 0); + gtk_box_pack_start (GTK_BOX(page), label, FALSE, FALSE, 0); + aboutwin = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(aboutwin), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(aboutwin), + gtk_box_pack_start (GTK_BOX(page), aboutwin, TRUE, TRUE, 0); + text = gtk_imhtml_new (NULL, NULL); + gtk_container_add (GTK_CONTAINER(aboutwin), text); + pidgin_setup_imhtml (text); + gtk_imhtml_append_text (GTK_IMHTML(text), + _("Use the <b>Autoprofile</b> portion of the <b>Tools</b> " + "buddy list</b> to configure the actual content that will go in your " + "status messages and profiles and set options.<br><br>"), + gtk_imhtml_append_text (GTK_IMHTML(text), + _("<u>DOCUMENTATION / HELP</u><br>"), GTK_IMHTML_NO_SCROLL); + gtk_imhtml_append_text (GTK_IMHTML(text), + _("Complete documentation can be found at:<br> <a href=" + "\"http://hkn.eecs.berkeley.edu/~casey/autoprofile/documentation.php\">" + "hkn.eecs.berkeley.edu/~casey/autoprofile/documentation.php</a><br>"), + gtk_imhtml_append_text (GTK_IMHTML(text), + _("<br><u>ABOUT</u><br>"), GTK_IMHTML_NO_SCROLL); + "<font size=\"3\"><b>", _("Developers"), ":</b></font><br>" + " Casey Ho (Lead Developer)<br>" + " Mitchell Harwell<br>", NULL); + gtk_imhtml_append_text(GTK_IMHTML(text), str, GTK_IMHTML_NO_SCROLL); + "<font size=\"3\"><b>", _("Contributors/Patchers"), ":</b></font><br>" + " Michael Milligan<br>" + " Mark Painter<br>", NULL); + gtk_imhtml_append_text(GTK_IMHTML(text), str, GTK_IMHTML_NO_SCROLL); + "<font size=\"3\"><b>", _("Website"), ":</b></font><br>" + " <a href=\"http://autoprofile.sourceforge.net\">" + "autoprofile.sourceforge.net<br>", NULL); + gtk_imhtml_append_text(GTK_IMHTML(text), str, GTK_IMHTML_NO_SCROLL); +/*---------------------------------------------------------------------------- + *--------------------------------------------------------------------------*/ +/* PRIMARILY RIPPED FROM GAIM GTKACCOUNT.C */ + PurpleAccount *account; +} PidginAccountAddUserData; + GtkTreeViewColumn *screenname_col; +static void add_account_to_liststore(PurpleAccount *, gpointer); +static void set_account(GtkListStore *, GtkTreeIter *, PurpleAccount *); +static gboolean is_profile_settable (PurpleAccount *a) { + const gchar *id = purple_account_get_protocol_id (a); + if (!strcmp (id, "prpl-yahoo") || + !strcmp (id, "prpl-msn") || + !strcmp (id, "prpl-jabber")) { +drag_data_get_cb(GtkWidget *widget, GdkDragContext *ctx, + GtkSelectionData *data, guint info, guint time, + AccountsWindow *dialog) + if (data->target == gdk_atom_intern("PURPLE_ACCOUNT", FALSE)) { + GtkTreeRowReference *ref; + GtkTreePath *source_row; + PurpleAccount *account = NULL; + ref = g_object_get_data(G_OBJECT(ctx), "gtk-tree-view-source-row"); + source_row = gtk_tree_row_reference_get_path(ref); + if (source_row == NULL) return; + gtk_tree_model_get_iter(GTK_TREE_MODEL(dialog->model), &iter, source_row); + gtk_tree_model_get_value(GTK_TREE_MODEL(dialog->model), &iter, + dialog->drag_iter = iter; + account = g_value_get_pointer(&val); + gtk_selection_data_set(data, gdk_atom_intern("PURPLE_ACCOUNT", FALSE), + 8, (void *)&account, sizeof(account)); + gtk_tree_path_free(source_row); +move_account_after(GtkListStore *store, GtkTreeIter *iter, + PurpleAccount *account; + gtk_tree_model_get(GTK_TREE_MODEL(store), iter, COLUMN_DATA, &account, -1); + gtk_list_store_insert_after(store, &new_iter, position); + set_account(store, &new_iter, account); + gtk_list_store_remove(store, iter); +move_account_before(GtkListStore *store, GtkTreeIter *iter, + PurpleAccount *account; + gtk_tree_model_get(GTK_TREE_MODEL(store), iter, COLUMN_DATA, &account, -1); + gtk_list_store_insert_before(store, &new_iter, position); + set_account(store, &new_iter, account); + gtk_list_store_remove(store, iter); +drag_data_received_cb(GtkWidget *widget, GdkDragContext *ctx, + guint x, guint y, GtkSelectionData *sd, + guint info, guint t, AccountsWindow *dialog) + if (sd->target == gdk_atom_intern("PURPLE_ACCOUNT", FALSE) && sd->data) { + PurpleAccount *a = NULL; + GtkTreePath *path = NULL; + GtkTreeViewDropPosition position; + memcpy(&a, sd->data, sizeof(a)); + if (gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget), x, y, + PurpleAccount *account; + gtk_tree_model_get_iter(GTK_TREE_MODEL(dialog->model), &iter, path); + gtk_tree_model_get_value(GTK_TREE_MODEL(dialog->model), &iter, + account = g_value_get_pointer(&val); + case GTK_TREE_VIEW_DROP_AFTER: + case GTK_TREE_VIEW_DROP_INTO_OR_AFTER: + move_account_after(dialog->model, &dialog->drag_iter, &iter); + dest_index = g_list_index(purple_accounts_get_all(), account) + 1; + case GTK_TREE_VIEW_DROP_BEFORE: + case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE: + dest_index = g_list_index(purple_accounts_get_all(), account); + move_account_before(dialog->model, &dialog->drag_iter, &iter); + purple_accounts_reorder(a, dest_index); +enabled_cb(GtkCellRendererToggle *renderer, gchar *path_str, gpointer data) + AccountsWindow *dialog = (AccountsWindow *)data; + PurpleAccount *account; + GtkTreeModel *model = GTK_TREE_MODEL(dialog->model); + gtk_tree_model_get_iter_from_string(model, &iter, path_str); + gtk_tree_model_get(model, &iter, + COLUMN_ENABLED, &enabled, + /* Change profile settings */ + ap_account_enable_profile (account, !enabled); + set_account (dialog->model, &iter, account); +add_columns(GtkWidget *treeview, AccountsWindow *dialog) + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + /* Screen Name column */ + column = gtk_tree_view_column_new(); + gtk_tree_view_column_set_title(column, _("Screen Name")); + gtk_tree_view_insert_column(GTK_TREE_VIEW(treeview), column, -1); + gtk_tree_view_column_set_resizable(column, TRUE); + renderer = gtk_cell_renderer_pixbuf_new(); + gtk_tree_view_column_pack_start(column, renderer, FALSE); + gtk_tree_view_column_add_attribute(column, renderer, "pixbuf", COLUMN_ICON); + renderer = gtk_cell_renderer_text_new(); + gtk_tree_view_column_pack_start(column, renderer, TRUE); + gtk_tree_view_column_add_attribute(column, renderer, + "text", COLUMN_SCREENNAME); + dialog->screenname_col = column; + renderer = gtk_cell_renderer_toggle_new(); + g_signal_connect(G_OBJECT(renderer), "toggled", + G_CALLBACK(enabled_cb), dialog); + gtk_tree_view_column_new_with_attributes(_("AutoProfile sets user info"), + renderer, "active", COLUMN_ENABLED, NULL); + gtk_tree_view_insert_column(GTK_TREE_VIEW(treeview), column, -1); + gtk_tree_view_column_set_resizable(column, TRUE); + column = gtk_tree_view_column_new(); + gtk_tree_view_column_set_title(column, _("Protocol")); + gtk_tree_view_insert_column(GTK_TREE_VIEW(treeview), column, -1); + gtk_tree_view_column_set_resizable(column, TRUE); + renderer = gtk_cell_renderer_text_new(); + gtk_tree_view_column_pack_start(column, renderer, TRUE); + gtk_tree_view_column_add_attribute(column, renderer, + "text", COLUMN_PROTOCOL); +set_account(GtkListStore *store, GtkTreeIter *iter, PurpleAccount *account) + pixbuf = pidgin_create_prpl_icon(account, 0.5); + scale = gdk_pixbuf_scale_simple(pixbuf, 16, 16, GDK_INTERP_BILINEAR); + if (purple_account_is_disconnected(account)) + gdk_pixbuf_saturate_and_pixelate(scale, scale, 0.0, FALSE); + gtk_list_store_set(store, iter, + COLUMN_SCREENNAME, purple_account_get_username(account), + COLUMN_ENABLED, ap_account_has_profile_enabled(account), + COLUMN_PROTOCOL, purple_account_get_protocol_name(account), + if (pixbuf != NULL) g_object_unref(G_OBJECT(pixbuf)); + if (scale != NULL) g_object_unref(G_OBJECT(scale)); +add_account_to_liststore(PurpleAccount *account, gpointer user_data) + AccountsWindow *dialog = (AccountsWindow *) user_data; + if (dialog == NULL) return; + if (!is_profile_settable (account)) return; + gtk_list_store_append(dialog->model, &iter); + set_account(dialog->model, &iter, account); +populate_accounts_list(AccountsWindow *dialog) + gtk_list_store_clear(dialog->model); + for (l = purple_accounts_get_all(); l != NULL; l = l->next) + add_account_to_liststore((PurpleAccount *)l->data, dialog); +#if !GTK_CHECK_VERSION(2,2,0) +get_selected_helper(GtkTreeModel *model, GtkTreePath *path, + GtkTreeIter *iter, gpointer user_data) + *((gboolean *)user_data) = TRUE; +account_selected_cb(GtkTreeSelection *sel, AccountsWindow *dialog) + gboolean selected = FALSE; +#if GTK_CHECK_VERSION(2,2,0) + selected = (gtk_tree_selection_count_selected_rows(sel) > 0); + gtk_tree_selection_selected_foreach(sel, get_selected_helper, &selected); +create_accounts_list(AccountsWindow *dialog) + GtkTargetEntry gte[] = {{"PURPLE_ACCOUNT", GTK_TARGET_SAME_APP, 0}}; + /* Create the scrolled window. */ + sw = gtk_scrolled_window_new(0, 0); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw), GTK_SHADOW_IN); + /* Create the list model. */ + dialog->model = gtk_list_store_new(NUM_COLUMNS, + GDK_TYPE_PIXBUF, G_TYPE_STRING, + G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_POINTER, + /* And now the actual treeview */ + treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(dialog->model)); + dialog->treeview = treeview; + gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(treeview), TRUE); + sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview)); + gtk_tree_selection_set_mode(sel, GTK_SELECTION_MULTIPLE); + g_signal_connect(G_OBJECT(sel), "changed", + G_CALLBACK(account_selected_cb), dialog); + gtk_container_add(GTK_CONTAINER(sw), treeview); + gtk_widget_show(treeview); + add_columns(treeview, dialog); + populate_accounts_list(dialog); + /* Setup DND. I wanna be an orc! */ + gtk_tree_view_enable_model_drag_source( + GTK_TREE_VIEW(treeview), GDK_BUTTON1_MASK, gte, + gtk_tree_view_enable_model_drag_dest( + GTK_TREE_VIEW(treeview), gte, 1, + GDK_ACTION_COPY | GDK_ACTION_MOVE); + g_signal_connect(G_OBJECT(treeview), "drag-data-received", + G_CALLBACK(drag_data_received_cb), dialog); + g_signal_connect(G_OBJECT(treeview), "drag-data-get", + G_CALLBACK(drag_data_get_cb), dialog); +static void account_page_delete_cb (GtkObject *object, gpointer data) +GtkWidget *get_account_page () { + AccountsWindow *accounts_window; + page = gtk_vbox_new (FALSE, 8); + gtk_container_set_border_width (GTK_CONTAINER (page), 12); + accounts_window = g_new0(AccountsWindow, 1); + /* Setup the scrolled window that will contain the list of accounts. */ + sw = create_accounts_list(accounts_window); + gtk_box_pack_start(GTK_BOX(page), sw, TRUE, TRUE, 0); + label = gtk_label_new ( + _("Accounts that do not support user-specified profiles are not shown")); + gtk_box_pack_start(GTK_BOX(page), label, FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (page), "destroy", + G_CALLBACK (account_page_delete_cb), accounts_window); +/*---------------------------------------------------------------------------- + *--------------------------------------------------------------------------*/ +void ap_gtk_prefs_add_summary_option (GtkWidget *widget) { + pidgin_prefs_dropdown (widget, + "Show AutoProfile summary window", + "/plugins/gtk/autoprofile/show_summary", + "Always", "always", "When away", "away", "Never", "never", NULL); +set_idle_away(PurpleSavedStatus *status) + purple_prefs_set_int("/core/savedstatus/idleaway", + purple_savedstatus_get_creation_time(status)); +static GtkWidget *get_behavior_page () { + GtkWidget *frame, *vbox, *hbox; + GtkWidget *button, *select, *menu; + page = gtk_vbox_new (FALSE, 8); + gtk_container_set_border_width (GTK_CONTAINER (page), 12); + /*---------- Update frequency ----------*/ + frame = pidgin_make_frame (page, _("Update frequency")); + vbox = gtk_vbox_new (FALSE, 0); + gtk_container_add (GTK_CONTAINER (frame), vbox); + pidgin_prefs_labeled_spin_button (vbox, + _("Minimum number of seconds between updates"), + "/plugins/gtk/autoprofile/delay_update", + label = gtk_label_new (""); + markup = g_markup_printf_escaped ("<span style=\"italic\">%s</span>", + _("WARNING: Using values below 60 seconds may increase the frequency\n" + "of rate limiting errors")); + gtk_label_set_markup (GTK_LABEL (label), markup); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); + /*----------- Auto-away stuff ------------*/ + frame = pidgin_make_frame(page, _("Auto-away")); + button = pidgin_prefs_checkbox(_("Change status when idle"), + "/plugins/gtk/autoprofile/away_when_idle", frame); + sg = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + select = pidgin_prefs_labeled_spin_button(frame, + _("Minutes before changing status:"), "/core/away/mins_before_away", + g_signal_connect(G_OBJECT(button), "clicked", + G_CALLBACK(pidgin_toggle_sensitive), select); + hbox = gtk_hbox_new(FALSE, 0); + gtk_container_add(GTK_CONTAINER(frame), hbox); + label = gtk_label_new_with_mnemonic(_("Change status to:")); + gtk_size_group_add_widget(sg, label); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + g_signal_connect(G_OBJECT(button), "clicked", + G_CALLBACK(pidgin_toggle_sensitive), label); + gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); + /* TODO: Show something useful if we don't have any saved statuses. */ + menu = pidgin_status_menu(purple_savedstatus_get_idleaway(), + G_CALLBACK(set_idle_away)); + gtk_box_pack_start(GTK_BOX(frame), menu, FALSE, FALSE, 0); + g_signal_connect(G_OBJECT(button), "clicked", + G_CALLBACK(pidgin_toggle_sensitive), menu); + gtk_label_set_mnemonic_widget(GTK_LABEL(label), menu); + if (!purple_prefs_get_bool("/plugins/gtk/autoprofile/away_when_idle")) { + gtk_widget_set_sensitive(GTK_WIDGET(menu), FALSE); + gtk_widget_set_sensitive(GTK_WIDGET(select), FALSE); + gtk_widget_set_sensitive(GTK_WIDGET(label), FALSE); +/*---------------------------------------------------------------------------- + *--------------------------------------------------------------------------*/ +/* Update string arguments */ +static gboolean update_behavior_string (GtkWidget *widget, GdkEventFocus *evt, + ap_debug ("preferences", "behavior string preference modified"); + if (!strcmp (data, "text_trigger")) { + purple_prefs_set_string ("/plugins/gtk/autoprofile/autorespond/trigger", + gtk_entry_get_text (GTK_ENTRY (widget))); + } else if (!strcmp (data, "text_respond")) { + purple_prefs_set_string ("/plugins/gtk/autoprofile/autorespond/text", + gtk_entry_get_text (GTK_ENTRY (widget))); + ap_debug_error ("preferences", "invalid data argument to string update"); +/* Update value returned from spinner for auto-respond delay */ +static gboolean update_delay_respond (GtkWidget *widget, GdkEventFocus *evt, + purple_prefs_set_int ("/plugins/gtk/autoprofile/delay_respond", + gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (widget))); +static GtkWidget *get_autoreply_page () { + GtkWidget *label, *checkbox, *spinner, *entry; + GtkWidget *frame, *vbox, *large_vbox, *hbox; + page = gtk_vbox_new (FALSE, 8); + gtk_container_set_border_width (GTK_CONTAINER (page), 12); + frame = pidgin_make_frame(page, _("General")); + dd = pidgin_prefs_dropdown(frame, _("Auto-reply:"), + PURPLE_PREF_STRING, "/plugins/gtk/autoprofile/autorespond/auto_reply", + _("When away"), "away", + _("When both away and idle"), "awayidle", + sg = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + gtk_size_group_add_widget(sg, dd); + gtk_misc_set_alignment(GTK_MISC(dd), 0, 0.5); + /*---------- Auto-responses ----------*/ + frame = pidgin_make_frame (page, _("Dynamic auto-responses")); + vbox = gtk_vbox_new (FALSE, 5); + gtk_container_add (GTK_CONTAINER (frame), vbox); + /* Auto-response activated */ + checkbox = pidgin_prefs_checkbox ( + _("Allow users to request more auto-responses"), + "/plugins/gtk/autoprofile/autorespond/enable", vbox); + large_vbox = gtk_vbox_new (FALSE, 5); + gtk_box_pack_start (GTK_BOX (vbox), large_vbox, FALSE, FALSE, 0); + /* Auto-response delay */ + hbox = gtk_hbox_new (FALSE, 5); + gtk_box_pack_start (GTK_BOX (large_vbox), hbox, FALSE, FALSE, 0); + label = gtk_label_new (_("Delay")); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + spinner = gtk_spin_button_new_with_range (1, G_MAXINT, 1); + gtk_box_pack_start (GTK_BOX (hbox), spinner, TRUE, TRUE, 0); + label = gtk_label_new (_("seconds between auto-responses")); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + gtk_spin_button_set_value (GTK_SPIN_BUTTON (spinner), purple_prefs_get_int ( + "/plugins/gtk/autoprofile/autorespond/delay")); + g_signal_connect (G_OBJECT (spinner), "value-changed", + G_CALLBACK (update_delay_respond), NULL); + /* Auto-response message string */ + label = gtk_label_new (_("Message sent with first autoresponse:")); + gtk_box_pack_start (GTK_BOX (large_vbox), label, FALSE, FALSE, 0); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + entry = gtk_entry_new (); + gtk_box_pack_start (GTK_BOX (large_vbox), entry, FALSE, FALSE, 0); + gtk_entry_set_max_length (GTK_ENTRY (entry), 100); + gtk_entry_set_text (GTK_ENTRY (entry), purple_prefs_get_string ( + "/plugins/gtk/autoprofile/autorespond/text")); + g_signal_connect (G_OBJECT (entry), "focus-out-event", + G_CALLBACK (update_behavior_string), "text_respond"); + label = gtk_label_new (_("Request trigger message:")); + gtk_box_pack_start (GTK_BOX (large_vbox), label, FALSE, FALSE, 0); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + entry = gtk_entry_new (); + gtk_box_pack_start (GTK_BOX (large_vbox), entry, FALSE, FALSE, 0); + gtk_entry_set_max_length (GTK_ENTRY (entry), 50); + gtk_entry_set_text (GTK_ENTRY (entry), purple_prefs_get_string ( + "/plugins/gtk/autoprofile/autorespond/trigger")); + g_signal_connect (G_OBJECT (entry), "focus-out-event", + G_CALLBACK (update_behavior_string), "text_trigger"); + /* Sensitivity signals */ + g_signal_connect(G_OBJECT(checkbox), "clicked", + G_CALLBACK(pidgin_toggle_sensitive), large_vbox); + if (!purple_prefs_get_bool ("/plugins/gtk/autoprofile/autorespond/enable")) { + gtk_widget_set_sensitive (large_vbox, FALSE); + gtk_widget_set_sensitive (large_vbox, TRUE); +/*---------------------------------------------------------------------------- + *--------------------------------------------------------------------------*/ +static GtkWidget *get_config_frame (PurplePlugin *plugin) + GtkWidget *info = get_info_page (); + gtk_widget_set_size_request (info, 350, 400); +static void dialog_cb (GtkDialog *dialog, gint arg1, gpointer user_data) + gtk_widget_destroy ((GtkWidget *)dialog); +void ap_preferences_display () + GtkWidget *dialog, *notebook; + notebook = gtk_notebook_new (); + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), + get_behavior_page (), gtk_label_new (_("General"))); + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), + get_account_page (), gtk_label_new (_("User info/profiles"))); + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), + get_autoreply_page (), gtk_label_new (_("Auto-reply"))); + g_object_set (notebook, "homogeneous", TRUE, NULL); + dialog = gtk_dialog_new_with_buttons(PIDGIN_ALERT_TITLE, NULL, + GTK_DIALOG_NO_SEPARATOR, + GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, + gtk_container_add (GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), notebook); + gtk_window_set_default_size (GTK_WINDOW(dialog), 400, 400); + gtk_widget_show_all (dialog); + g_signal_connect (G_OBJECT(dialog), "response", + G_CALLBACK(dialog_cb), dialog); +/*--------------- Generate the preference widget once ----------------*/ +PidginPluginUiInfo ui_info = --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/autoprofile/sizes.h Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,49 @@
+/*--------------------------------------------------------------------------* + * A Purple away message and profile manager that supports dynamic text * + * AutoProfile is the legal property of its developers. 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 * + *--------------------------------------------------------------------------*/ +/* Size definitions (0 indicates not compatible) */ +#define AP_SIZE_MAXIMUM 2048 +#define AP_SIZE_PROFILE_MAX 2048 +#define AP_SIZE_PROFILE_AIM 2048 +#define AP_SIZE_PROFILE_ICQ 0 +#define AP_SIZE_PROFILE_JABBER 0 +#define AP_SIZE_PROFILE_MSN 0 +#define AP_SIZE_PROFILE_YAHOO 0 +/* Away message sizes */ +#define AP_SIZE_AWAY_MAX 2048 +#define AP_SIZE_AWAY_AIM 2048 +/* Available message sizes */ +#define AP_SIZE_AVAILABLE_MAX 512 +#define AP_SIZE_AVAILABLE_AIM 60 +#define AP_SIZE_AVAILABLE_ICQ 0 +#define AP_SIZE_AVAILABLE_MSN 0 +#define AP_SIZE_AVAILABLE_YAHOO 512 +/* End size definitions */ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/autoprofile/utility.c Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,221 @@
+/*--------------------------------------------------------------------------* + * A Purple away message and profile manager that supports dynamic text * + * AutoProfile is the legal property of its developers. 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 "autoprofile.h" +/* TODO: get rid of this and port to glib time */ +static GStaticMutex time_mutex = G_STATIC_MUTEX_INIT; +static struct tm *ap_tm_copy (const struct tm *t) { + r = (struct tm *) malloc (sizeof (struct tm)); + r->tm_hour = t->tm_hour; + r->tm_mday = t->tm_mday; + r->tm_year = t->tm_year; + r->tm_wday = t->tm_wday; + r->tm_yday = t->tm_yday; + r->tm_isdst = t->tm_isdst; +struct tm *ap_localtime (const time_t *tp) { + g_static_mutex_lock (&time_mutex); + result = ap_tm_copy (localtime (tp)); + g_static_mutex_unlock (&time_mutex); +struct tm *ap_gmtime (const time_t *tp) { + g_static_mutex_lock (&time_mutex); + result = ap_tm_copy (gmtime (tp)); + g_static_mutex_unlock (&time_mutex); +/* Reads from fortune-style file and returns GList of each quote */ +static void fortune_helper (GString *s, gchar *data, gboolean escape_html) { + g_string_append_printf (s, "<br>"); + case '"': g_string_append_printf (s, """); return; + case '&': g_string_append_printf (s, "&"); return; + case '<': g_string_append_printf (s, "<"); return; + case '>': g_string_append_printf (s, ">"); return; + g_string_append_unichar (s, g_utf8_get_char (data)); +GList *read_fortune_file (const char *filename, gboolean escape_html) + gchar *raw_data, *raw_data_start; + gchar *converted, *text; + if (!g_file_test (filename, G_FILE_TEST_EXISTS)) { + if (!g_file_get_contents (filename, &text, NULL, NULL)) { + converted = purple_utf8_try_convert (text); + if (converted != NULL) { + raw_data_start = raw_data = purple_utf8_salvage (text); + purple_str_strip_char (raw_data, '\r'); + /* Modeling the parser as a finite state machine */ + cur_quote = g_string_new (""); + /* State after newline (potential quote) */ + if (*raw_data == '%') { // Found it + quotes = g_list_append (quotes, strdup (cur_quote->str)); + g_string_truncate (cur_quote, 0); + g_string_append_printf (cur_quote, "<br>"); + fortune_helper (cur_quote, raw_data, escape_html); + /* State after end of a quote */ + if (*raw_data != '\n' && *raw_data != '%') { + fortune_helper (cur_quote, raw_data, escape_html); + if (*raw_data == '\n') { + fortune_helper (cur_quote, raw_data, escape_html); + raw_data = g_utf8_next_char (raw_data); + if (strlen (cur_quote->str) > 0) { + quotes = g_list_append (quotes, strdup (cur_quote->str)); + g_string_free (cur_quote, TRUE); +/* Returns 1 if a pattern is found at the start of a string */ +int match_start (const char *text, const char *pattern) + if (!*text || *pattern++ != *text++) +/* Free's a GList as well as the internal contents */ +void free_string_list (GList *list) +/* Check if string is in GList */ +gboolean string_list_find (GList *lst, const char *data) + if (!strcmp (data, (char *) lst->data)) { +/* Prints out debug messages with repetitive formatting completed */ +static void auto_debug_helper ( + PurpleDebugLevel level, const char *category, const char *message) + g_string_printf (s, "%s: %s\n", category, message); + purple_debug (level, "autoprofile", s->str); + g_string_free (s, TRUE); +void ap_debug (const char *category, const char *message) { + auto_debug_helper (PURPLE_DEBUG_INFO, category, message); +void ap_debug_misc (const char *category, const char *message) { + auto_debug_helper (PURPLE_DEBUG_MISC, category, message); +void ap_debug_warn (const char *category, const char *message) { + auto_debug_helper (PURPLE_DEBUG_WARNING, category, message); +void ap_debug_error (const char *category, const char *message) { + auto_debug_helper (PURPLE_DEBUG_ERROR, category, message); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/autoprofile/utility.h Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,41 @@
+/*--------------------------------------------------------------------------* + * A Purple away message and profile manager that supports dynamic text * + * AutoProfile is the legal property of its developers. 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 * + *--------------------------------------------------------------------------*/ +struct tm *ap_localtime (const time_t *); +struct tm *ap_gmtime (const time_t *); +GList *read_fortune_file (const char *, gboolean); +int match_start (const char *, const char *); +void free_string_list (GList *); +gboolean string_list_find (GList *, const char *); +void ap_debug (const char *, const char *); +void ap_debug_misc (const char *, const char *); +void ap_debug_warn (const char *, const char *); +void ap_debug_error (const char *, const char *); +time_t rfc_parse_date_time (const char *data); +int rfc_parse_was_gmt (); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/autoprofile/utility_rfc822.c Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,187 @@
+/*--------------------------------------------------------------------------* + * A Purple away message and profile manager that supports dynamic text * + * AutoProfile is the legal property of its developers. 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 * + *--------------------------------------------------------------------------*/ +static struct tm parsed_datetime; +static int parsed_gmttime = 0; +/* Strip leading whitespace */ +static const char* rfc_parse_whitespace (const char *data) { + while (*data && isspace (*data)) +/* Strip leading digits */ +static const char* rfc_parse_num (const char *data) { + while (*data && isdigit (*data)) +/* Strip leading whitespace and digits */ +static const char* rfc_parse_whitespace_num (const char *data) { + while (*data && (isspace (*data) || isdigit (*data))) +static const char* rfc_parse_date (const char *data) { + sscanf (data, "%d", &day); + data = rfc_parse_whitespace_num (data); + sscanf (data, "%s", month); + if (!strcmp (month, "Jan")) { monthnum = 0; } else + if (!strcmp (month, "Feb")) { monthnum = 1; } else + if (!strcmp (month, "Mar")) { monthnum = 2; } else + if (!strcmp (month, "Apr")) { monthnum = 3; } else + if (!strcmp (month, "May")) { monthnum = 4; } else + if (!strcmp (month, "Jun")) { monthnum = 5; } else + if (!strcmp (month, "Jul")) { monthnum = 6; } else + if (!strcmp (month, "Aug")) { monthnum = 7; } else + if (!strcmp (month, "Sep")) { monthnum = 8; } else + if (!strcmp (month, "Oct")) { monthnum = 9; } else + if (!strcmp (month, "Nov")) { monthnum = 10; } else + if (!strcmp (month, "Dec")) { monthnum = 11; } + sscanf (data, "%d", &year); + data = rfc_parse_whitespace (data); + data = rfc_parse_num (data); + } else if (year > 100) { + parsed_datetime.tm_mday = day; + parsed_datetime.tm_mon = monthnum; + parsed_datetime.tm_year = year; +static const char* rfc_parse_hour (const char *data) { + sscanf (data, "%d", &hour); + data = strchr (data, ':'); + sscanf (++data, "%d", &minutes); + if (strchr (data, ':')) { + data = strchr (data, ':') + 1; + sscanf (data, "%d", &seconds); + data = rfc_parse_whitespace_num (data); + parsed_datetime.tm_hour = hour; + parsed_datetime.tm_min = minutes; + parsed_datetime.tm_sec = seconds; +static const char *rfc_parse_zone (const char *data) { + if (strstr (data, "GMT")) +static const char* rfc_parse_time (const char *data) { + data = rfc_parse_hour (data); + data = rfc_parse_zone (data); +static const char* rfc_parse_day (const char *data) { + return strchr (data, ',') + 1; +int rfc_parse_was_gmt () { +time_t rfc_parse_date_time (const char *data) { + /* Initialize values */ + parsed_datetime.tm_sec = 0; + parsed_datetime.tm_min = 0; + parsed_datetime.tm_hour = 0; + parsed_datetime.tm_mday = 0; + parsed_datetime.tm_mon = 0; + parsed_datetime.tm_year = 0; + parsed_datetime.tm_isdst = -1; + data = rfc_parse_whitespace (data); + data = rfc_parse_day (data); + data = rfc_parse_date (data); + data = rfc_parse_time (data); + result = mktime(&parsed_datetime); + if (rfc_parse_was_gmt ()) + struct tm *x = rfc_parse_date_time ("Mon, 06 Jun 2005 20:24:18 GMT"); + printf ("Sec: %d\n", x->tm_sec); + printf ("Min: %d\n", x->tm_min); + printf ("Hour: %d\n", x->tm_hour); + printf ("Day: %d\n", x->tm_mday); + printf ("Month: %d\n", x->tm_mon); + printf ("Year: %d\n", x->tm_year); + printf ("GMT: %d\n", parsed_gmttime); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/autoprofile/widget.c Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,607 @@
+/*--------------------------------------------------------------------------* + * A Purple away message and profile manager that supports dynamic text * + * AutoProfile is the legal property of its developers. 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 "../common/pp_internal.h" +static GStaticMutex widget_mutex = G_STATIC_MUTEX_INIT; +static GList *widgets = NULL; +static GHashTable *identifiers = NULL; +static char *widget_pref = "/plugins/gtk/autoprofile/widgets/widget_ids"; +static void ap_widget_init_default_statuses () + // Make sure we don't keep on readding the default statuses if a user + if (!purple_prefs_exists (widget_pref)) { + purple_prefs_add_none ("/plugins/gtk/autoprofile/widgets/42"); + purple_prefs_add_string ( + "/plugins/gtk/autoprofile/widgets/42/component", "Timestamp"); + purple_prefs_add_string ( + "/plugins/gtk/autoprofile/widgets/42/alias", "Timestamp"); + purple_prefs_add_string ( + "/plugins/gtk/autoprofile/widgets/42/timestamp_format", + "Automatically created at %I:%M %p"); +void ap_widget_init () { + ap_widget_init_default_statuses (); + node = g_list_append (NULL, g_strdup ("42")); + purple_prefs_add_string_list (widget_pref, node); + free_string_list (node); +static gchar *strip_whitespace (const gchar *text) { + gchar *result, *end, *search; + while (isspace (*text)) { + search = result = g_strdup (text); + if (end == NULL && isspace (*search)) { + if (!isspace (*search)) { + if (end != NULL) *end = '\0'; +static void update_widget_ids () { + struct widget *cur_widget; + for (cur_node = widgets; cur_node != NULL; cur_node = cur_node->next) { + cur_widget = (struct widget *) cur_node->data; + ids = g_list_append (ids, cur_widget->wid); + purple_prefs_set_string_list (widget_pref, ids); +// Mutex is ALREADY HELD when this function is called +static struct widget *ap_widget_find_internal (const gchar *search_text) { + struct widget *cur_widget; + alias = strip_whitespace (search_text); + cur_widget = (struct widget *) cur_node->data; + if (!purple_utf8_strcasecmp (alias, cur_widget->alias)) { + cur_node = cur_node->next; +struct widget *ap_widget_find (const gchar *search_text) { + g_static_mutex_lock (&widget_mutex); + w = ap_widget_find_internal (search_text); + g_static_mutex_unlock (&widget_mutex); +struct widget *ap_widget_find_by_identifier (const gchar *search_text) { + g_static_mutex_lock (&widget_mutex); + w = (struct widget *) g_hash_table_lookup (identifiers, search_text); + g_static_mutex_unlock (&widget_mutex); +void ap_widget_start () { + GList *widget_identifiers, *widget_identifiers_start; + const gchar *identifier, *component_identifier; + struct component *comp; + g_static_mutex_lock (&widget_mutex); + identifiers = g_hash_table_new (g_str_hash, g_str_equal); + pref_name = g_string_new (""); + widget_identifiers_start = purple_prefs_get_string_list (widget_pref); + for (widget_identifiers = widget_identifiers_start; + widget_identifiers != NULL; + widget_identifiers = widget_identifiers->next) { + g_string_printf (pref_name, + "/plugins/gtk/autoprofile/widgets/%s/component", + (gchar *) widget_identifiers->data); + component_identifier = purple_prefs_get_string (pref_name->str); + if (component_identifier == NULL) { + ap_debug_error ("widget", "widget does not have component information"); + comp = ap_component_get_component (component_identifier); + ap_debug_error ("widget", "no component matches widget identifier"); + g_string_printf (pref_name, + "/plugins/gtk/autoprofile/widgets/%s/alias", + (gchar *) widget_identifiers->data); + identifier = purple_prefs_get_string (pref_name->str); + if (identifier == NULL) { + ap_debug_error ("widget", "widget does not have alias information"); + w = ap_widget_find_internal (identifier); + ap_debug_error ("widget", "widget alias already in use"); + w = (struct widget *) malloc (sizeof (struct widget)); + w->alias = g_strdup (identifier); + w->wid = g_strdup ((gchar *) widget_identifiers->data); + w->data = g_hash_table_new (NULL, NULL); + widgets = g_list_append (widgets, w); + g_hash_table_insert (identifiers, w->wid, w); + if (w->component->load) { + w->component->load (w); + g_string_printf (pref_name, + "loaded saved widget with alias %s and identifier %s", + ap_debug_misc ("widget", pref_name->str); + free_string_list (widget_identifiers_start); + g_string_free (pref_name, TRUE); + g_static_mutex_unlock (&widget_mutex); + ap_widget_gtk_start (); +void ap_widget_finish () { + g_static_mutex_lock (&widget_mutex); + ap_widget_gtk_finish (); + g_hash_table_destroy (identifiers); + w = (struct widget *) widgets->data; + if (w->component->unload) { + w->component->unload (w); + g_hash_table_destroy (w->data); + g_list_free_1 (widgets); + g_static_mutex_unlock (&widget_mutex); +gboolean ap_widget_has_content_changed () { + gboolean changed = FALSE; + g_static_mutex_lock (&widget_mutex); + for (node = widgets; node != NULL; node = node->next) { + w = (struct widget *) node->data; + if (w->component->has_content_changed == NULL || + w->component->has_content_changed (w)) { + g_static_mutex_unlock (&widget_mutex); +GList *ap_widget_get_widgets () { + g_static_mutex_lock (&widget_mutex); + result = g_list_copy (widgets); + g_static_mutex_unlock (&widget_mutex); +struct widget *ap_widget_create (struct component *comp) + gchar *identifier, *alias; + g_static_mutex_lock (&widget_mutex); + // Sanity check to make sure we dont "delete" old widgets by + if (identifiers == NULL) { + ap_debug_warn ("widget", + "tried to create widget when variables unitialized"); + g_static_mutex_unlock (&widget_mutex); + ap_debug ("widget", "instantiating new widget from component"); + w = ap_widget_find_internal (comp->identifier); + alias = NULL; // Stupid compiler + alias = g_strdup (comp->identifier); + for (i = 1; i < 10000; i++) { + g_string_printf (s, "%s%d", comp->identifier, i); + w = ap_widget_find_internal (s->str); + alias = g_strdup (s->str); + // This would happen....very very rarely... + ap_debug_error ("widget", "ran out of aliases for component"); + g_string_free (s, TRUE); + g_static_mutex_unlock (&widget_mutex); + g_string_printf (s, "%d", i); + w = (struct widget *) node->data; + if (!strcmp (s->str, w->wid)) { + identifier = g_strdup (s->str); + w = (struct widget *) malloc (sizeof (struct widget)); + w->data = g_hash_table_new (NULL, NULL); + widgets = g_list_append (widgets, w); + g_hash_table_insert (identifiers, w->wid, w); + g_string_printf (s, "/plugins/gtk/autoprofile/widgets/%s", w->wid); + purple_prefs_add_none (s->str); + g_string_printf (s, "/plugins/gtk/autoprofile/widgets/%s/component", + purple_prefs_add_string (s->str, w->component->identifier); + g_string_printf (s, "/plugins/gtk/autoprofile/widgets/%s/alias", w->wid); + purple_prefs_add_string (s->str, w->alias); + if (w->component->init_pref) { + w->component->init_pref (w); + if (w->component->load) { + w->component->load (w); + g_string_printf (s, "Created widget with alias %s and identifier %s", + ap_debug ("widget", s->str); + g_string_free (s, TRUE); + g_static_mutex_unlock (&widget_mutex); +void ap_widget_delete (struct widget *w) { + ap_debug_error ("widget", "attempt to delete NULL widget"); + g_static_mutex_lock (&widget_mutex); + // Sanity check to make sure we dont "delete" old widgets by + if (identifiers == NULL) { + ap_debug_warn ("widget", + "tried to delete widget when variables unitialized"); + g_static_mutex_unlock (&widget_mutex); + g_string_printf (s, "Deleting widget with alias %s and identifier %s", + ap_debug ("widget", s->str); + widgets = g_list_remove (widgets, w); + g_hash_table_remove (identifiers, w->wid); + g_string_printf (s, "/plugins/gtk/autoprofile/widgets/%s", w->wid); + purple_prefs_remove (s->str); + g_string_free (s, TRUE); + if (w->component->unload) { + w->component->unload (w); + g_hash_table_destroy (w->data); + g_static_mutex_unlock (&widget_mutex); +// TRUE if rename succeeds, FALSE otherwise +gboolean ap_widget_rename (struct widget *orig, const gchar *new_alias) { + g_static_mutex_lock (&widget_mutex); + w = ap_widget_find_internal (new_alias); + if (w != NULL && w != orig) { + g_static_mutex_unlock (&widget_mutex); + orig_alias = orig->alias; + orig->alias = g_strdup (new_alias); + g_string_printf (s, "/plugins/gtk/autoprofile/widgets/%s/alias", orig->wid); + purple_prefs_set_string (s->str, new_alias); + g_string_printf (s, "Changed alias of widget from %s to %s", + orig_alias, new_alias); + ap_debug ("widget", s->str); + g_string_free (s, TRUE); + g_static_mutex_unlock (&widget_mutex); +/* Widget data galore! */ +void ap_widget_set_data (struct widget *w, int id, gpointer data) { + g_static_mutex_lock (&widget_mutex); + g_hash_table_insert (w->data, GINT_TO_POINTER(id), data); + g_static_mutex_unlock (&widget_mutex); +gpointer ap_widget_get_data (struct widget *w, int id) { + g_static_mutex_lock (&widget_mutex); + result = g_hash_table_lookup (w->data, GINT_TO_POINTER(id)); + g_static_mutex_unlock (&widget_mutex); +/* Widget preferences galore! */ +gchar *ap_prefs_get_pref_name (struct widget *w, const char *name) { + g_string_append (s, "/plugins/gtk/autoprofile/widgets/"); + g_string_append_printf (s, "%s/%s", w->wid, name); + g_string_free (s, FALSE); +void ap_prefs_add_bool (struct widget *w, const char *name, gboolean value) { + gchar *pref = ap_prefs_get_pref_name (w, name); + purple_prefs_add_bool (pref, value); +void ap_prefs_add_int (struct widget *w, const char *name, int value) { + gchar *pref = ap_prefs_get_pref_name (w, name); + purple_prefs_add_int (pref, value); +void ap_prefs_add_none (struct widget *w, const char *name) { + gchar *pref = ap_prefs_get_pref_name (w, name); + purple_prefs_add_none (pref); +void ap_prefs_add_string (struct widget *w, const char *name, + gchar *pref = ap_prefs_get_pref_name (w, name); + purple_prefs_add_string (pref, value); +void ap_prefs_add_string_list (struct widget *w, const char *name, + gchar *pref = ap_prefs_get_pref_name (w, name); + purple_prefs_add_string_list (pref, value); +gboolean ap_prefs_get_bool (struct widget *w, const char *name) { + gchar *pref = ap_prefs_get_pref_name (w, name); + result = purple_prefs_get_bool (pref); +int ap_prefs_get_int (struct widget *w, const char *name) { + gchar *pref = ap_prefs_get_pref_name (w, name); + result = purple_prefs_get_int (pref); +const char *ap_prefs_get_string (struct widget *w, const char *name) { + gchar *pref = ap_prefs_get_pref_name (w, name); + result = purple_prefs_get_string (pref); +GList *ap_prefs_get_string_list (struct widget *w, const char *name) { + gchar *pref = ap_prefs_get_pref_name (w, name); + result = purple_prefs_get_string_list (pref); +void ap_prefs_set_bool (struct widget *w, const char *name, gboolean value) { + gchar *pref = ap_prefs_get_pref_name (w, name); + purple_prefs_set_bool (pref, value); + ap_widget_prefs_updated (w); +void ap_prefs_set_int (struct widget *w, const char *name, int value) { + gchar *pref = ap_prefs_get_pref_name (w, name); + purple_prefs_set_int (pref, value); + ap_widget_prefs_updated (w); +void ap_prefs_set_string (struct widget *w, const char *name, + gchar *pref = ap_prefs_get_pref_name (w, name); + purple_prefs_set_string (pref, value); + ap_widget_prefs_updated (w); +void ap_prefs_set_string_list (struct widget *w, const char *name, + gchar *pref = ap_prefs_get_pref_name (w, name); + purple_prefs_set_string_list (pref, value); + ap_widget_prefs_updated (w); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/autoprofile/widget.h Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,96 @@
+/*--------------------------------------------------------------------------* + * A Purple away message and profile manager that supports dynamic text * + * AutoProfile is the legal property of its developers. 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 heart of everything */ + struct component *component; +void ap_widget_start (); +void ap_widget_finish (); +void ap_widget_gtk_start (); +void ap_widget_gtk_finish (); +gboolean ap_widget_has_content_changed (); +GList *ap_widget_get_widgets (); +struct widget *ap_widget_find (const gchar *); +struct widget *ap_widget_find_by_identifier (const gchar *); +struct widget *ap_widget_create (struct component *); +void ap_widget_delete (struct widget *); +// TRUE if rename succeeds, FALSE otherwise +gboolean ap_widget_rename (struct widget *, const gchar *); +GtkWidget *ap_widget_get_config_page (); +void ap_widget_prefs_updated (struct widget *); +GtkWidget *get_widget_list (GtkWidget *, GtkTreeSelection **); +void done_with_widget_list (); +/* Widget data galore! */ +void ap_widget_set_data (struct widget *, int, gpointer); +gpointer ap_widget_get_data (struct widget *, int); +/* Widget preferences galore! */ +gchar *ap_prefs_get_pref_name (struct widget *, const char *); +GtkWidget *ap_prefs_checkbox (struct widget *, const char *, const char *, +GtkWidget *ap_prefs_dropdown_from_list (struct widget *, GtkWidget *, + const gchar *, PurplePrefType, const char *, GList *); +GtkWidget *ap_prefs_labeled_entry (struct widget *, GtkWidget *page, + const gchar *, const char *, GtkSizeGroup *); +GtkWidget *ap_prefs_labeled_spin_button (struct widget *, GtkWidget *, + const gchar *, const char *, int, int, GtkSizeGroup *); +void ap_prefs_add_bool (struct widget *, const char *name, gboolean value); +void ap_prefs_add_int (struct widget *, const char *name, int value); +void ap_prefs_add_none (struct widget *, const char *name); +void ap_prefs_add_string (struct widget *, const char *, const char *); +void ap_prefs_add_string_list (struct widget *, const char *, GList *); +gboolean ap_prefs_get_bool (struct widget *, const char *name); +int ap_prefs_get_int (struct widget *, const char *name); +const char * ap_prefs_get_string (struct widget *, const char *name); +GList * ap_prefs_get_string_list (struct widget *, const char *name); +void ap_prefs_set_bool (struct widget *, const char *name, gboolean value); +void ap_prefs_set_int (struct widget *, const char *name, int value); +void ap_prefs_set_string (struct widget *, const char *name, const char *); +void ap_prefs_set_string_list (struct widget *, const char *, GList *); +#endif /* _AP_WIDGET_H_ */ --- a/autoreply/autoreply.c Tue Oct 09 06:21:19 2007 -0400
+++ b/autoreply/autoreply.c Mon Mar 24 05:08:00 2008 -0400
@@ -1,6 +1,6 @@
* Autoreply - Autoreply feature for all the protocols
+ * Copyright (C) 2005-2008 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net> * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
@@ -18,6 +18,7 @@
+/* If you can't figure out what this line is for, DON'T TOUCH IT. */ #include "../common/pp_internal.h"
#define PLUGIN_ID "core-plugin_pack-autoreply"
@@ -44,6 +45,7 @@
#define PREFS_MAXSEND PREFS_PREFIX "/maxsend"
#define PREFS_USESTATUS PREFS_PREFIX "/usestatus"
#define PREFS_PREFIX_MSG PREFS_PREFIX "/prefix"
+#define PREFS_X_INVISIBLE PREFS_PREFIX "/invisible" typedef struct _PurpleAutoReply PurpleAutoReply;
typedef struct _AutoReplyProtocolOptions AutoReplyProtocolOptions;
@@ -56,6 +58,7 @@
struct _AutoReplyProtocolOptions {
PurpleAccountOption *message;
+ PurpleAccountOption *off; @@ -87,31 +90,31 @@
reply = purple_savedstatus_get_message(purple_savedstatus_get_current());
+ if ((!reply || !*reply) && buddy) /* Is there any special auto-reply for this buddy? */
reply = purple_blist_node_get_string((PurpleBlistNode*)buddy, "autoreply");
- if (!reply && PURPLE_BLIST_NODE_IS_BUDDY((PurpleBlistNode*)buddy))
+ if ((!reply || !*reply) && PURPLE_BLIST_NODE_IS_BUDDY((PurpleBlistNode*)buddy)) /* Anything for the contact, then? */
reply = purple_blist_node_get_string(((PurpleBlistNode*)buddy)->parent, "autoreply");
/* Is there any specific auto-reply for this account? */
reply = purple_account_get_string(account, "autoreply", NULL);
/* Get the global auto-reply message */
reply = purple_prefs_get_string(PREFS_GLOBAL);
+ if (*reply == ' ' || *reply == '\0') if (!reply && use_status == STATUS_FALLBACK)
@@ -135,21 +138,28 @@
if (!message || !*message)
- /* Do not send an autoreply for an autoreply */
- if (flags & PURPLE_MESSAGE_AUTO_RESP)
+ /* Do not send an autoreply for an autoreply or a 'delayed' (offline?) message */ + if (flags & (PURPLE_MESSAGE_AUTO_RESP | PURPLE_MESSAGE_DELAYED)) + if(purple_account_get_bool(account, "ar_off", FALSE)) g_return_if_fail(purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM);
presence = purple_account_get_presence(account);
+ if (purple_prefs_get_bool(PREFS_X_INVISIBLE) && + purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_INVISIBLE)) if (purple_prefs_get_bool(PREFS_AWAY) && !purple_presence_is_available(presence))
if (purple_prefs_get_bool(PREFS_IDLE) && purple_presence_is_idle(presence))
buddy = purple_find_buddy(account, who);
reply = get_autoreply_message(buddy, account);
@@ -226,7 +236,7 @@
get_autoreply_message(buddy, account), TRUE, FALSE,
(gc->flags & PURPLE_CONNECTION_HTML) ? "html" : NULL,
_("_Save"), G_CALLBACK(set_auto_reply_cb),
account, purple_buddy_get_name(buddy), NULL, node);
@@ -252,13 +262,16 @@
AutoReplyProtocolOptions *arpo;
PurplePluginProtocolInfo *info = PURPLE_PLUGIN_PROTOCOL_INFO(plg);
arpo = g_new(AutoReplyProtocolOptions, 1);
arpo->message = purple_account_option_string_new(_("Autoreply message"),
+ arpo->off = purple_account_option_bool_new(_("Turn off autoreply"), info->protocol_options = g_list_append(info->protocol_options,
+ info->protocol_options = g_list_append(info->protocol_options, arpo->off); if (!g_hash_table_lookup(options, plg))
g_hash_table_insert(options, plg, arpo);
@@ -273,7 +286,7 @@
* 22:55 < sadrul> grim: the check when removing is required, iirc, when
* pidgin quits, and a prpl is unloaded before the plugin
@@ -282,9 +295,14 @@
info->protocol_options = g_list_remove_link(info->protocol_options, l);
purple_account_option_destroy(arpo->message);
- g_hash_table_remove(options, plg);
+ if ((l = g_list_find(info->protocol_options, arpo->off))) + info->protocol_options = g_list_remove_link(info->protocol_options, l); + purple_account_option_destroy(arpo->off); + g_hash_table_remove(options, plg); @@ -322,7 +340,7 @@
add_options_for_protocol(list->data);
@@ -376,13 +394,17 @@
_("Autoreply Prefix\n(only when necessary)"));
purple_plugin_pref_frame_add(frame, pref);
+ pref = purple_plugin_pref_new_with_name_and_label(PREFS_X_INVISIBLE, + _("Do not autoreply when invisible.")); + purple_plugin_pref_frame_add(frame, pref); pref = purple_plugin_pref_new_with_label(_("Status message"));
purple_plugin_pref_frame_add(frame, pref);
pref = purple_plugin_pref_new_with_name_and_label(PREFS_USESTATUS,
_("Autoreply with status message"));
purple_plugin_pref_set_type(pref, PURPLE_PLUGIN_PREF_CHOICE);
- purple_plugin_pref_add_choice(pref, _("Never"),
+ purple_plugin_pref_add_choice(pref, _("Never"), GINT_TO_POINTER(STATUS_NEVER));
purple_plugin_pref_add_choice(pref, _("Always when there is a status message"),
GINT_TO_POINTER(STATUS_ALWAYS));
@@ -479,6 +501,7 @@
purple_prefs_add_int(PREFS_MAXSEND, 10);
purple_prefs_add_int(PREFS_USESTATUS, STATUS_NEVER);
purple_prefs_add_string(PREFS_PREFIX_MSG, _("This is an autoreply: "));
+ purple_prefs_add_bool(PREFS_X_INVISIBLE, TRUE); PURPLE_INIT_PLUGIN(PLUGIN_STATIC_NAME, init_plugin, info)
--- a/awaynotify/awaynotify.c Tue Oct 09 06:21:19 2007 -0400
+++ b/awaynotify/awaynotify.c Mon Mar 24 05:08:00 2008 -0400
@@ -1,6 +1,6 @@
* awaynotify - show notices when status changes
- * Copyright (C) 2005-2006 Matt Perry <guy@somewhere.fscked.org>
+ * Copyright (C) 2005-2008 Matt Perry <guy@somewhere.fscked.org> * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -17,6 +17,7 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
+/* If you can't figure out what this line is for, DON'T TOUCH IT. */ #include "../common/pp_internal.h"
--- a/bash/bash.c Tue Oct 09 06:21:19 2007 -0400
+++ b/bash/bash.c Mon Mar 24 05:08:00 2008 -0400
@@ -1,6 +1,6 @@
- * Copyright (C) 2003-2005
+ * Copyright (C) 2003-2008 * See ../AUTHORS for a list of all authors
* bash: provides a /command to display the URL for a random or specified
@@ -21,6 +21,7 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
+/* If you can't figure out what this line is for, DON'T TOUCH IT. */ #include "../common/pp_internal.h"
--- a/bit/bit.c Tue Oct 09 06:21:19 2007 -0400
+++ b/bit/bit.c Mon Mar 24 05:08:00 2008 -0400
@@ -1,5 +1,5 @@
- * Copyright (C) 2005 Peter Lawler <bleeter from users.sf.net>
+ * Copyright (C) 2005-2008 Peter Lawler <bleeter from users.sf.net> * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -16,6 +16,7 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
+/* If you can't figure out what this line is for, DON'T TOUCH IT. */ #include "../common/pp_internal.h"
--- a/blistops/blistops.c Tue Oct 09 06:21:19 2007 -0400
+++ b/blistops/blistops.c Mon Mar 24 05:08:00 2008 -0400
@@ -1,6 +1,7 @@
* Hides the blist on signon (or when it's created)
- * Copyright (C) 2004 Gary Kramlich.
+ * Copyright (C) 2004-2008 Gary Kramlich + * Copyright (C) 2007-2008 Sadrul Habib Chowdhury * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
@@ -18,6 +19,7 @@
+/* If you can't figure out what this line is for, DON'T TOUCH IT. */ #include "../common/pp_internal.h"
--- a/broadcast/Makefile.am Tue Oct 09 06:21:19 2007 -0400
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,28 +0,0 @@
-broadcastdir = $(PURPLE_LIBDIR)
-broadcast_la_LDFLAGS = -module -avoid-version
-broadcast_LTLIBRARIES = broadcast.la
-broadcast_la_SOURCES = \
- -DLIBDIR=\"$(PURPLE_LIBDIR)\" \
- -DDATADIR=\"$(PURPLE_DATADIR)\" \
--- a/broadcast/Makefile.mingw Tue Oct 09 06:21:19 2007 -0400
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,11 +0,0 @@
-# Description: Makefile for broadcast plugin.
-include $(PP_TOP)/win_pp.mak
--- a/broadcast/broadcast.c Tue Oct 09 06:21:19 2007 -0400
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,187 +0,0 @@
- * Broadcast - Send an IM to your entire buddy list
- * Copyright (C) 2007 John Bailey <rekkanoryo@rekkanoryo.org>
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
-#include "../common/pp_internal.h"
-#define BROADCAST_CATEGORY "plugin pack: broadcast"
-/* These two functions directly ripped from groupmsg and renamed (shamelessly,
-broadcast_request_cancel_cb(GList *list, const char *text)
-broadcast_request_send_cb(GList *list, const char *message)
- PurpleConversation *conv = NULL;
- purple_markup_html_to_xhtml(message, NULL, &stripped);
- for (l = list; l; l = l->next) {
- conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, b->account, b->name);
- if (b->account->gc->flags & PURPLE_CONNECTION_HTML)
- purple_conv_im_send(PURPLE_CONV_IM(conv), message);
- purple_conv_im_send(PURPLE_CONV_IM(conv), stripped);
-broadcast_action_cb(PurplePluginAction *action)
- GList *buddies = NULL, *ltmp = NULL;
- const gchar *bname = NULL, *balias = NULL;
- PurpleBlistNode *root = NULL, *g = NULL, *c = NULL;
- PurpleBuddyList *blist = NULL;
- blist = purple_get_blist();
- for(g = root; g && PURPLE_BLIST_NODE_IS_GROUP(g); g = g->next) {
- for (c = g->child; c; c = c->next) {
- if(!PURPLE_BLIST_NODE_IS_CHAT(c)) {
- buddies = g_list_append(buddies,
- purple_contact_get_priority_buddy((PurpleContact *)c));
- purple_debug_info(BROADCAST_CATEGORY, "added a buddy to queue\n");
- purple_debug_error(BROADCAST_CATEGORY, "no buddies in queue!\n");
- if(g_list_length(buddies) > 6) { /* arbitrary number that I like */
- stmp = g_string_new(_("Your message will be sent to and possibly annoy"
- " EVERYONE on your buddy list!"));
- purple_debug_info(BROADCAST_CATEGORY,
- "too many buddies on list; showing generic message!\n");
- stmp = g_string_new("Your message will be sent to these buddies:\n");
- for(ltmp = buddies; ltmp; ltmp = ltmp->next) {
- b = (PurpleBuddy *)(ltmp->data);
- bname = purple_buddy_get_alias(b);
- balias = purple_buddy_get_alias(b);
- g_string_append_printf(stmp, " %s (%s)\n", balias, bname);
- purple_debug_info(BROADCAST_CATEGORY, "added %s (%s) to dialog string\n",
- purple_debug_info(BROADCAST_CATEGORY, "requesting message input\n");
- purple_request_input(action, _("Broadcast Spim"),
- _("Enter the message to send"), stmp->str, "", TRUE, FALSE, "html",
- _("Send"), G_CALLBACK(broadcast_request_send_cb), _("Cancel"),
- G_CALLBACK(broadcast_request_cancel_cb), NULL, NULL, NULL, buddies);
- g_string_free(stmp, TRUE);
-broadcast_actions(PurplePlugin *plugin, gpointer context)
- purple_debug_info(BROADCAST_CATEGORY, "creating the action list\n");
- return g_list_append(NULL, purple_plugin_action_new(_("Broadcast a Message"),
-broadcast_load(PurplePlugin *plugin)
- purple_debug_misc(BROADCAST_CATEGORY, "broadcast_load called\n");
-static PurplePluginInfo broadcast_info =
- PURPLE_PLUGIN_STANDARD,
- PURPLE_PRIORITY_DEFAULT,
- "core-plugin_pack-broadcast",
- "John Bailey <rekkanoryo@rekkanoryo.org>",
-broadcast_init(PurplePlugin *plugin) {
- bindtextdomain(GETTEXT_PACKAGE, PP_LOCALEDIR);
- bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
- broadcast_info.name = _("Broadcaster");
- broadcast_info.summary = _("Send an IM to everyone on your buddy list.");
- broadcast_info.description = _("Adds the ability to send an IM to every "
- "Purple Contact on your Buddy List");
-PURPLE_INIT_PLUGIN(broadcast, broadcast_init, broadcast_info)
--- a/buddytime/Makefile.am Tue Oct 09 06:21:19 2007 -0400
+++ b/buddytime/Makefile.am Mon Mar 24 05:08:00 2008 -0400
@@ -1,23 +1,45 @@
-EXTRA_DIST = .incomplete .pidgin-plugin
+EXTRA_DIST = .incomplete .purple-plugin .pidgin-plugin +noinst_PROGRAMS = recursetest -buddytimedir = $(PIDGIN_LIBDIR)
-buddytime_la_LDFLAGS = -module -avoid-version
-buddytime_LTLIBRARIES = buddytime.la
+noinst_PROGRAMS += gtktimezonetest +gtktimezonetest_SOURCES = \ -buddytime_la_SOURCES = \
+gtktimezonetest_LDADD = \
+gtkbuddytimedir = $(PIDGIN_LIBDIR) +gtkbuddytime_la_LDFLAGS = -module -avoid-version +gtkbuddytime_LTLIBRARIES = gtkbuddytime.la +gtkbuddytime_la_SOURCES = \ +gtkbuddytime_la_LIBADD = \ +buddytimedir = $(PURPLE_LIBDIR) +buddytime_la_LDFLAGS = -module -avoid-version +buddytime_LTLIBRARIES = buddytime.la +buddytime_la_SOURCES = \ -DLIBDIR=\"$(PIDGIN_LIBDIR)\" \
-DDATADIR=\"$(PIDGIN_DATADIR)\" \
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytime/buddyedit.c Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,429 @@
+/************************************************************************* + * A Gaim plugin that adds an edit to to buddies allowing you to change + * various details you can't normally change. It also provides a mechanism + * for subsequent plugins to add themselves to that dialog. + * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006 + * Some code copyright (C) 2006, Richard Laager <rlaager@users.sf.net> + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + *************************************************************************/ +#define PLUGIN "core-kleptog-buddyedit" +#include "gaim-compat.h" +#include "debug.h" /* Debug output functions */ +#include "request.h" /* Requests stuff */ +static GaimPlugin *plugin_self; +buddyedit_editcomplete_cb(GaimBlistNode * data, GaimRequestFields * fields) + gboolean blist_destroy = FALSE; + GaimBlistNode *olddata = data; /* Keep pointer in case we need to destroy it */ + /* Account detail stuff */ + case GAIM_BLIST_BUDDY_NODE: + GaimBuddy *buddy = (GaimBuddy *) data; + GaimAccount *account = gaim_request_fields_get_account(fields, "account"); + const char *name = gaim_request_fields_get_string(fields, "name"); + const char *alias = gaim_request_fields_get_string(fields, "alias"); + /* If any details changes, create the buddy */ + if((account != buddy->account) || strcmp(name, buddy->name)) + GaimBuddy *newbuddy = gaim_buddy_new(account, name, alias); + gaim_blist_add_buddy(newbuddy, NULL, NULL, data); /* Copy it to correct location */ + /* Now this is ugly, but we want to copy the settings and avoid issues with memory management */ + tmp = ((GaimBlistNode *) buddy)->settings; + ((GaimBlistNode *) buddy)->settings = ((GaimBlistNode *) newbuddy)->settings; + ((GaimBlistNode *) newbuddy)->settings = tmp; + data = (GaimBlistNode *) newbuddy; + gaim_blist_alias_buddy(buddy, alias); + case GAIM_BLIST_CONTACT_NODE: + GaimContact *contact = (GaimContact *) data; + const char *alias = gaim_request_fields_get_string(fields, "alias"); + gaim_contact_set_alias(contact, alias); + case GAIM_BLIST_GROUP_NODE: + GaimGroup *group = (GaimGroup *) data; + const char *alias = gaim_request_fields_get_string(fields, "alias"); + gaim_blist_rename_group(group, alias); + case GAIM_BLIST_CHAT_NODE: + GaimChat *chat = (GaimChat *) data; + gboolean new_chat = FALSE; + GList *list = NULL, *tmp; + gc = gaim_account_get_connection(chat->account); + if(GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info != NULL) + list = GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info(gc); + GaimAccount *newaccount = gaim_request_fields_get_account(fields, "account"); + /* In Gaim2 each prot_chat_entry has a field "required". We use + * this to determine if a field is important enough to recreate + * the chat if it changes. Non-required fields we jsut change + * in-situ. In Gaim1.5 this field doesn't exist so we always +#if GAIM_MAJOR_VERSION >= 2 + if(newaccount != chat->account) + const char *oldvalue, *newvalue; + for (tmp = g_list_first(list); tmp && !new_chat; tmp = g_list_next(tmp)) + struct proto_chat_entry *pce = tmp->data; + if(!pce->required) /* Only checking required fields at this point */ + continue; /* Not yet */ + oldvalue = g_hash_table_lookup(chat->components, pce->identifier); + newvalue = gaim_request_fields_get_string(fields, pce->identifier); + if(strcmp(oldvalue, newvalue) != 0) + const char *oldvalue, *newvalue; + GHashTable *components = + g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + for (tmp = g_list_first(list); tmp; tmp = g_list_next(tmp)) + struct proto_chat_entry *pce = tmp->data; + oldvalue = g_hash_table_lookup(chat->components, pce->identifier); + g_hash_table_replace(components, g_strdup(pce->identifier), + newvalue = gaim_request_fields_get_string(fields, pce->identifier); + g_hash_table_replace(components, g_strdup(pce->identifier), + GaimChat *newchat = gaim_chat_new(newaccount, NULL, components); + gaim_blist_add_chat(newchat, NULL, data); /* Copy it to correct location */ + data = (GaimBlistNode *) newchat; + else /* Just updating values in old chat */ + for (tmp = g_list_first(list); tmp; tmp = g_list_next(tmp)) + struct proto_chat_entry *pce = tmp->data; +#if GAIM_MAJOR_VERSION >= 2 + newvalue = gaim_request_fields_get_string(fields, pce->identifier); + g_hash_table_replace(chat->components, g_strdup(pce->identifier), + const char *alias = gaim_request_fields_get_string(fields, "alias"); + gaim_blist_alias_chat(chat, alias); + case GAIM_BLIST_OTHER_NODE: + gaim_signal_emit(gaim_blist_get_handle(), PLUGIN "-submit-fields", fields, data); + if(olddata->type == GAIM_BLIST_BUDDY_NODE) + gaim_blist_remove_buddy((GaimBuddy *) olddata); + else if(olddata->type == GAIM_BLIST_CHAT_NODE) + gaim_blist_remove_chat((GaimChat *) olddata); + gaim_blist_schedule_save(); +static GaimAccount *buddyedit_account_filter_func_data; +buddyedit_account_filter_func(GaimAccount * account) + GaimPluginProtocolInfo *gppi1 = + GAIM_PLUGIN_PROTOCOL_INFO(gaim_account_get_connection(account)->prpl); + GaimPluginProtocolInfo *gppi2 = + GAIM_PLUGIN_PROTOCOL_INFO(gaim_account_get_connection(buddyedit_account_filter_func_data)-> +/* Node is either a contact or a buddy */ +buddy_edit_cb(GaimBlistNode * node, gpointer data) + gaim_debug(GAIM_DEBUG_INFO, PLUGIN, "buddy_edit_cb(%p)\n", node); + GaimRequestFields *fields; + GaimRequestField *field; + GaimRequestFieldGroup *group; + char *request_title = NULL; + fields = gaim_request_fields_new(); + case GAIM_BLIST_BUDDY_NODE: + GaimBuddy *buddy = (GaimBuddy *) node; + group = gaim_request_field_group_new("Buddy Details"); + gaim_request_fields_add_group(fields, group); + field = gaim_request_field_account_new("account", "Account", buddy->account); + gaim_request_field_account_set_show_all(field, TRUE); + gaim_request_field_group_add_field(group, field); + field = gaim_request_field_string_new("name", "Name", buddy->name, FALSE); + gaim_request_field_group_add_field(group, field); + field = gaim_request_field_string_new("alias", "Alias", buddy->alias, FALSE); + gaim_request_field_group_add_field(group, field); + request_title = "Edit Buddy"; + case GAIM_BLIST_CONTACT_NODE: + GaimContact *contact = (GaimContact *) node; + group = gaim_request_field_group_new("Contact Details"); + gaim_request_fields_add_group(fields, group); + field = gaim_request_field_string_new("alias", "Alias", contact->alias, FALSE); + gaim_request_field_group_add_field(group, field); + request_title = "Edit Contact"; + case GAIM_BLIST_GROUP_NODE: + GaimGroup *grp = (GaimGroup *) node; + group = gaim_request_field_group_new("Group Details"); + gaim_request_fields_add_group(fields, group); + field = gaim_request_field_string_new("alias", "Name", grp->name, FALSE); + gaim_request_field_group_add_field(group, field); + request_title = "Edit Group"; + case GAIM_BLIST_CHAT_NODE: + GaimChat *chat = (GaimChat *) node; + group = gaim_request_field_group_new("Chat Details"); + gaim_request_fields_add_group(fields, group); + field = gaim_request_field_account_new("account", "Account", chat->account); + gaim_request_field_account_set_filter(field, buddyedit_account_filter_func); + buddyedit_account_filter_func_data = chat->account; + gaim_request_field_group_add_field(group, field); + GList *list = NULL, *tmp; + gc = gaim_account_get_connection(chat->account); + if(GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info != NULL) + list = GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info(gc); + for (tmp = g_list_first(list); tmp; tmp = g_list_next(tmp)) + struct proto_chat_entry *pce = tmp->data; +#if GAIM_MAJOR_VERSION >= 2 + gaim_debug(GAIM_DEBUG_INFO, PLUGIN, + "identifier=%s, label=%s, is_int=%d, required=%d\n", pce->identifier, + pce->label, pce->is_int, pce->required); + continue; /* Not yet */ + value = g_hash_table_lookup(chat->components, pce->identifier); + field = gaim_request_field_string_new(pce->identifier, pce->label, value, FALSE); +#if GAIM_MAJOR_VERSION >= 2 + gaim_request_field_set_required(field, pce->required); + gaim_request_field_group_add_field(group, field); + field = gaim_request_field_string_new("alias", "Alias", chat->alias, FALSE); + gaim_request_field_group_add_field(group, field); + request_title = "Edit Chat"; + request_title = "Edit"; + gaim_signal_emit(gaim_blist_get_handle(), PLUGIN "-create-fields", fields, node); + gaim_request_fields(plugin_self, + request_title, NULL, NULL, + fields, "OK", G_CALLBACK(buddyedit_editcomplete_cb), "Cancel", NULL, node); +buddy_menu_cb(GaimBlistNode * node, GList ** menu, void *data) +#if GAIM_MAJOR_VERSION < 2 + GaimBlistNodeAction *action; + GaimMenuAction *action; + /* These are the types we handle */ + case GAIM_BLIST_BUDDY_NODE: + case GAIM_BLIST_CONTACT_NODE: + case GAIM_BLIST_GROUP_NODE: + case GAIM_BLIST_CHAT_NODE: + case GAIM_BLIST_OTHER_NODE: +#if GAIM_MAJOR_VERSION < 2 + action = gaim_blist_node_action_new("Edit...", buddy_edit_cb, NULL); + action = gaim_menu_action_new("Edit...", GAIM_CALLBACK(buddy_edit_cb), NULL, NULL); + *menu = g_list_append(*menu, action); +plugin_load(GaimPlugin * plugin) + void *blist_handle = gaim_blist_get_handle(); + gaim_signal_register(blist_handle, PLUGIN "-create-fields", /* Called when about to create dialog */ + gaim_marshal_VOID__POINTER_POINTER, NULL, 2, /* (FieldList*,BlistNode*) */ + gaim_value_new(GAIM_TYPE_SUBTYPE, GAIM_TYPE_POINTER), /* FieldList */ + gaim_value_new(GAIM_TYPE_SUBTYPE, GAIM_SUBTYPE_BLIST)); + gaim_signal_register(blist_handle, PLUGIN "-submit-fields", /* Called when dialog submitted */ + gaim_marshal_VOID__POINTER_POINTER, NULL, 2, /* (FieldList*,BlistNode*) */ + gaim_value_new(GAIM_TYPE_SUBTYPE, GAIM_TYPE_POINTER), /* FieldList */ + gaim_value_new(GAIM_TYPE_SUBTYPE, GAIM_SUBTYPE_BLIST)); + gaim_signal_connect(blist_handle, "blist-node-extended-menu", plugin, + GAIM_CALLBACK(buddy_menu_cb), NULL); +static GaimPluginInfo info = { + G_STRINGIFY(PLUGIN_VERSION), + "Enable editing of buddy properties", + "A plugin that adds an edit to to buddies allowing you to change various details you can't normally change. " + "It also provides a mechanism for subsequent plugins to add themselves to that dialog. ", + "Martijn van Oosterhout <kleptog@svana.org>", + "http://svana.org/kleptog/gaim/", +init_plugin(GaimPlugin * plugin) +PURPLE_INIT_PLUGIN(buddyedit, init_plugin, info); --- a/buddytime/buddytime.c Tue Oct 09 06:21:19 2007 -0400
+++ b/buddytime/buddytime.c Mon Mar 24 05:08:00 2008 -0400
@@ -1,6 +1,13 @@
- * Plugin Name - Summary
+ * Buddy Time - Displays a buddy's local time + * A libpurple plugin that allows you to configure a timezone on a per-contact + * basis so it can display the localtime of your contact when a conversation + * starts. Convenient if you deal with contacts from many parts of the + * Copyright (C) 2006-2007, Richard Laager <rlaager@pidgin.im> + * Copyright (C) 2006, Martijn van Oosterhout <kleptog@svana.org> * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
@@ -14,247 +21,470 @@
* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +/* If you can't figure out what this line is for, DON'T TOUCH IT. */ #include "../common/pp_internal.h"
-#define PLUGIN_ID "gtk-plugin_pack-buddytime"
-#define PLUGIN_STATIC_NAME "buddytime"
-#define PLUGIN_AUTHOR "Gary Kramlich <grim@reaperworld.com>"
+#define PLUGIN_STATIC_NAME CORE_PLUGIN_STATIC_NAME +#define PLUGIN_ID CORE_PLUGIN_ID
+#define SETTING_NAME "timezone" +#define CONTROL_NAME PLUGIN_ID "-" SETTING_NAME
-/******************************************************************************
- *****************************************************************************/
-#define BT_NODE_SETTING "bt-timezone"
+#include "conversation.h" -/******************************************************************************
- *****************************************************************************/
- PurpleRequestField *timezone;
+#define TIMEZONE_FLAG ((void*)1) +#define DISABLED_FLAG ((void*)2) +BuddyTimeUiOps *ui_ops = NULL; +static PurplePlugin *plugin_self;
- PurpleConversation *conv;
-/******************************************************************************
- *****************************************************************************/
-static GList *dialogs = NULL;
-static GList *widgets = NULL;
+/* Resolve specifies what the return value should mean: + * If TRUE, it's for display, we want to know the *effect* thus hiding the + * "none" value and going to going level to find the default + * If false, we only want what the user enter, thus the string "none" if + * data is here so we can use this as a callback for IPC +buddy_get_timezone(PurpleBlistNode * node, gboolean resolve, void *data) + PurpleBlistNode *datanode = NULL; -/******************************************************************************
- *****************************************************************************/
-bt_widget_new(PurpleConversation *conv) {
+ case PURPLE_BLIST_BUDDY_NODE: + datanode = (PurpleBlistNode *) purple_buddy_get_contact((PurpleBuddy *) node); + case PURPLE_BLIST_CONTACT_NODE: + case PURPLE_BLIST_GROUP_NODE: + timezone = purple_blist_node_get_string(datanode, SETTING_NAME); - g_return_val_if_fail(conv, NULL);
- ret = g_new0(BTWidget, 1);
+ /* The effect of "none" is to stop recursion */ + if (timezone && strcmp(timezone, "none") == 0) - ret->ebox = gtk_event_box_new();
+ if (datanode->type == PURPLE_BLIST_CONTACT_NODE) + /* There is no purple_blist_contact_get_group(), though there probably should be */ + datanode = datanode->parent; + timezone = purple_blist_node_get_string(datanode, SETTING_NAME); - ret->label = gtk_label_new("label");
- gtk_container_add(GTK_CONTAINER(ret->ebox), ret->label);
+ if (timezone && strcmp(timezone, "none") == 0) -/******************************************************************************
- *****************************************************************************/
-bt_dialog_ok_cb(gpointer data, PurpleRequestFields *fields) {
- BTDialog *dialog = (BTDialog *)data;
+/* Calcuates the difference between two struct tm's. */ +timezone_calc_difference(struct tm *remote_tm, struct tm *tmp_tm) - dialogs = g_list_remove(dialogs, dialog);
+ /* Note this only works because the times are + * known to be within 24 hours of each other! */ + if (remote_tm->tm_mday != tmp_tm->tm_mday) + hours_diff += (remote_tm->tm_hour - tmp_tm->tm_hour); + minutes_diff = (remote_tm->tm_min - tmp_tm->tm_min); + return (double)minutes_diff / 60.0 + hours_diff;
-bt_dialog_cancel_cb(gpointer data, PurpleRequestFields *fields) {
- BTDialog *dialog = (BTDialog *)data;
+/* data is here so we can use this as a callback for IPC */ +timezone_get_time(const char *timezone, struct tm *tm, double *diff, void *data) + struct state *tzinfo = timezone_load(timezone); + localsub(&now, 0, tm, tzinfo); + /* Store the current TZ value. */ + old_tz = g_getenv("TZ"); - dialogs = g_list_remove(dialogs, dialog);
+ g_setenv("TZ", timezone, TRUE); + tm_tmp = localtime(&now); + *tm = *tm_tmp; /* Must copy, localtime uses local buffer */ + /* Reset the old TZ value. */ + g_setenv("TZ", old_tz, TRUE); + /* Calculate user's localtime, and compare. If same, no output */ + tm_tmp = localtime(&now); + if (tm_tmp->tm_hour == tm->tm_hour && tm_tmp->tm_min == tm->tm_min) + *diff = timezone_calc_difference(tm, tm_tmp); -bt_show_dialog(PurpleBlistNode *node) {
- PurpleRequestFields *fields;
- PurpleRequestFieldGroup *group;
- PurpleAccount *account = NULL;
+timezone_createconv_cb(PurpleConversation * conv, void *data) + if (purple_conversation_get_type(conv) != PURPLE_CONV_TYPE_IM) - dialog = g_new0(BTDialog, 1);
+ name = purple_conversation_get_name(conv); + buddy = purple_find_buddy(purple_conversation_get_account(conv), name);
+ timezone = buddy_get_timezone((PurpleBlistNode *) buddy, TRUE, NULL);
+ ret = timezone_get_time(timezone, &tm, &diff, NULL); - current = purple_blist_node_get_int(node, BT_NODE_SETTING);
- /* TODO: set account from node */
+ const char *text = purple_time_format(&tm); - /* build the request fields */
- fields = purple_request_fields_new();
- group = purple_request_field_group_new(NULL);
- purple_request_fields_add_group(fields, group);
+ str = g_strdup_printf(dngettext(GETTEXT_PACKAGE, + "Remote Local Time: %s (%.4g hour behind)", + "Remote Local Time: %s (%.4g hours behind)", diff), + str = g_strdup_printf(dngettext(GETTEXT_PACKAGE, + "Remote Local Time: %s (%.4g hour ahead)", + "Remote Local Time: %s (%.4g hours ahead)", diff), - dialog->timezone = purple_request_field_choice_new("timezone",
- purple_request_field_group_add_field(group, dialog->timezone);
+ purple_conversation_write(conv, PLUGIN_STATIC_NAME, str, PURPLE_MESSAGE_SYSTEM, time(NULL)); - purple_request_field_choice_add(dialog->timezone, _("Clear setting"));
- purple_request_field_choice_add(dialog->timezone, _("GMT-12"));
- purple_request_field_choice_add(dialog->timezone, _("GMT-11"));
- purple_request_field_choice_add(dialog->timezone, _("GMT-10"));
- purple_request_field_choice_add(dialog->timezone, _("GMT-9"));
- purple_request_field_choice_add(dialog->timezone, _("GMT-8"));
- purple_request_field_choice_add(dialog->timezone, _("GMT-7"));
- purple_request_field_choice_add(dialog->timezone, _("GMT-6"));
- purple_request_field_choice_add(dialog->timezone, _("GMT-5"));
- purple_request_field_choice_add(dialog->timezone, _("GMT-4"));
- purple_request_field_choice_add(dialog->timezone, _("GMT-3"));
- purple_request_field_choice_add(dialog->timezone, _("GMT-2"));
- purple_request_field_choice_add(dialog->timezone, _("GMT-1"));
- purple_request_field_choice_add(dialog->timezone, _("GMT"));
- purple_request_field_choice_add(dialog->timezone, _("GMT+1"));
- purple_request_field_choice_add(dialog->timezone, _("GMT+2"));
- purple_request_field_choice_add(dialog->timezone, _("GMT+3"));
- purple_request_field_choice_add(dialog->timezone, _("GMT+4"));
- purple_request_field_choice_add(dialog->timezone, _("GMT+5"));
- purple_request_field_choice_add(dialog->timezone, _("GMT+6"));
- purple_request_field_choice_add(dialog->timezone, _("GMT+7"));
- purple_request_field_choice_add(dialog->timezone, _("GMT+8"));
- purple_request_field_choice_add(dialog->timezone, _("GMT+9"));
- purple_request_field_choice_add(dialog->timezone, _("GMT+10"));
- purple_request_field_choice_add(dialog->timezone, _("GMT+11"));
- purple_request_field_choice_add(dialog->timezone, _("GMT+12"));
+buddytimezone_submitfields_cb(PurpleRequestFields * fields, PurpleBlistNode * data) + PurpleRequestField *list; + purple_debug(PURPLE_DEBUG_INFO, PLUGIN_STATIC_NAME, "buddytimezone_submitfields_cb(%p,%p)\n", fields, data); + case PURPLE_BLIST_BUDDY_NODE: + node = (PurpleBlistNode *) purple_buddy_get_contact((PurpleBuddy *) data); + case PURPLE_BLIST_CONTACT_NODE: + case PURPLE_BLIST_GROUP_NODE: + /* code handles either case */ + case PURPLE_BLIST_CHAT_NODE: + case PURPLE_BLIST_OTHER_NODE: -// purple_request_field_choice_set_default_value(dialog->timezone, current);
-// purple_request_field_coice_set_value(dialog->timezone, current);
+ list = purple_request_fields_get_field(fields, CONTROL_NAME); + if (ui_ops != NULL && ui_ops->get_timezone_menu_selection != NULL) + const char *seldata = ui_ops->get_timezone_menu_selection(list->ui_data); + purple_blist_node_remove_setting(node, SETTING_NAME); + purple_blist_node_set_string(node, SETTING_NAME, seldata); + sellist = purple_request_field_list_get_selected(list); + seldata = purple_request_field_list_get_data(list, sellist->data); - /* TODO: set who from blist node */
- purple_request_fields(NULL, _("Select timezone"),
- _("OK"), PURPLE_CALLBACK(bt_dialog_ok_cb),
- _("Cancel"), PURPLE_CALLBACK(bt_dialog_cancel_cb),
- account, NULL /* who */, NULL, dialog);
- dialogs = g_list_append(dialogs, dialog);
+ /* Otherwise, it's fixed value and this means deletion */ + if (seldata == TIMEZONE_FLAG) + purple_blist_node_set_string(node, SETTING_NAME, sellist->data); + else if (seldata == DISABLED_FLAG) + purple_blist_node_set_string(node, SETTING_NAME, "none"); + purple_blist_node_remove_setting(node, SETTING_NAME); +buddy_add_timezone_cb(char *filename, void *data) + PurpleRequestField *field = (PurpleRequestField *) data; + if (isupper(filename[0])) + purple_request_field_list_add(field, filename, TIMEZONE_FLAG); -bt_edit_timezone_cb(PurpleBlistNode *node, gpointer data) {
+buddytimezone_createfields_cb(PurpleRequestFields * fields, PurpleBlistNode * data) + purple_debug(PURPLE_DEBUG_INFO, PLUGIN_STATIC_NAME, "buddytimezone_createfields_cb(%p,%p)\n", fields, data); + PurpleRequestField *field; + PurpleRequestFieldGroup *group; + case PURPLE_BLIST_BUDDY_NODE: + case PURPLE_BLIST_CONTACT_NODE: + case PURPLE_BLIST_GROUP_NODE: + case PURPLE_BLIST_CHAT_NODE: + case PURPLE_BLIST_OTHER_NODE: + group = purple_request_field_group_new(NULL); + purple_request_fields_add_group(fields, group); + timezone = buddy_get_timezone(data, FALSE, NULL); + if (ui_ops != NULL && ui_ops->create_menu) + purple_request_field_new(CONTROL_NAME, + is_default ? "Default timezone for group" : "Timezone of contact", + PURPLE_REQUEST_FIELD_LIST); + field->ui_data = ui_ops->create_menu(timezone); + purple_request_field_list_new(CONTROL_NAME, + is_default ? "Default timezone for group" : + "Timezone of contact (type to select)"); + purple_request_field_list_set_multi_select(field, FALSE); + purple_request_field_list_add(field, "<Default>", ""); + purple_request_field_list_add(field, "<Disabled>", DISABLED_FLAG); + recurse_directory("/usr/share/zoneinfo/", buddy_add_timezone_cb, field); + if (strcmp(timezone, "none") == 0) + purple_request_field_list_add_selected(field, "<Disabled>"); + purple_request_field_list_add_selected(field, timezone); + purple_request_field_list_add_selected(field, "<Default>"); + purple_request_field_group_add_field(group, field); +marshal_POINTER__POINTER_BOOL(PurpleCallback cb, va_list args, void *data, + void *arg1 = va_arg(args, void *); + gboolean arg2 = va_arg(args, gboolean); + ret_val = ((gpointer (*)(void *, gboolean, void *))cb)(arg1, arg2, data); + if (return_val != NULL) -bt_blist_drawing_menu_cb(PurpleBlistNode *node, GList **menu) {
- PurpleMenuAction *action;
- if (purple_blist_node_get_flags(node) & PURPLE_BLIST_NODE_FLAG_NO_SAVE)
+marshal_POINTER__POINTER_POINTER_POINTER(PurpleCallback cb, va_list args, void *data, + void *arg1 = va_arg(args, void *); + void *arg2 = va_arg(args, void *); + void *arg3 = va_arg(args, void *); - /* ignore chats and groups */
- if(PURPLE_BLIST_NODE_IS_CHAT(node) || PURPLE_BLIST_NODE_IS_GROUP(node))
- action = purple_menu_action_new(_("Timezone"),
- PURPLE_CALLBACK(bt_edit_timezone_cb),
- (*menu) = g_list_append(*menu, action);
+ ret_val = ((gpointer (*)(void *, void *, void *, void *))cb)(arg1, arg2, arg3, data); + if (return_val != NULL) -/******************************************************************************
- *****************************************************************************/
+load_ui_plugin(gpointer data) + PurplePlugin *ui_plugin; + ui_plugin_id = g_strconcat(purple_core_get_ui(), "-", PLUGIN_STATIC_NAME, NULL); + ui_plugin = purple_plugins_find_with_id(ui_plugin_id); + if (!purple_plugin_load(ui_plugin)) + purple_notify_error(ui_plugin, NULL, _("Failed to load the Buddy Timezone UI."), + ui_plugin->error ? ui_plugin->error : ""); -/******************************************************************************
- *****************************************************************************/
-plugin_load(PurplePlugin *plugin) {
- purple_signal_connect(purple_blist_get_handle(),
- "blist-node-extended-menu",
- PURPLE_CALLBACK(bt_blist_drawing_menu_cb),
+plugin_load(PurplePlugin * plugin) + purple_signal_connect(purple_conversations_get_handle(), "conversation-created", plugin, + PURPLE_CALLBACK(timezone_createconv_cb), NULL); + purple_plugin_ipc_register(plugin, BUDDYTIME_BUDDY_GET_TIMEZONE, + PURPLE_CALLBACK(buddy_get_timezone), + marshal_POINTER__POINTER_BOOL, + purple_value_new(PURPLE_TYPE_STRING), + purple_value_new(PURPLE_TYPE_SUBTYPE, + PURPLE_SUBTYPE_BLIST_NODE), + purple_value_new(PURPLE_TYPE_BOOLEAN)); + purple_plugin_ipc_register(plugin, BUDDYTIME_TIMEZONE_GET_TIME, + PURPLE_CALLBACK(timezone_get_time), + marshal_POINTER__POINTER_POINTER_POINTER, + purple_value_new(PURPLE_TYPE_INT), + purple_value_new(PURPLE_TYPE_POINTER), + purple_value_new(PURPLE_TYPE_POINTER)); + /* This is done as an idle callback to avoid an infinite loop + * when we try to load the UI plugin which depends on this plugin + * which isn't officially loaded yet. */ + purple_timeout_add(0, load_ui_plugin, NULL);
-plugin_unload(PurplePlugin *plugin) {
-static PurplePluginInfo info = {
- PURPLE_PLUGIN_MAGIC, /* Magic */
- PURPLE_MAJOR_VERSION, /* Purple Major Version */
- PURPLE_MINOR_VERSION, /* Purple Minor Version */
- PURPLE_PLUGIN_STANDARD, /* plugin type */
- PIDGIN_PLUGIN_TYPE, /* ui requirement */
- NULL, /* dependencies */
- PURPLE_PRIORITY_DEFAULT, /* priority */
- PLUGIN_ID, /* plugin id */
- PP_VERSION, /* version */
- NULL, /* description */
- PLUGIN_AUTHOR, /* author */
- PP_WEBSITE, /* website */
- plugin_load, /* load */
- plugin_unload, /* unload */
+static PurplePluginInfo info = + PURPLE_PLUGIN_STANDARD, /**< type */ + NULL, /**< ui_requirement */ + NULL, /**< dependencies */ + PURPLE_PRIORITY_DEFAULT, /**< priority */ + PP_VERSION, /**< version */ + NULL, /**< description */ + PLUGIN_AUTHOR, /**< author */ + PP_WEBSITE, /**< homepage */ + plugin_load, /**< load */ + NULL, /**< extra_info */ + NULL, /**< prefs_info */ + NULL, /**< reserved 1 */ + NULL, /**< reserved 2 */ + NULL, /**< reserved 3 */ + NULL /**< reserved 4 */ -init_plugin(PurplePlugin *plugin) {
+init_plugin(PurplePlugin * plugin) bindtextdomain(GETTEXT_PACKAGE, PP_LOCALEDIR);
bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
info.name = _("Buddy Time");
- info.summary = _("summary");
- info.description = _("description");
+ info.summary = _("Quickly see the local time of a buddy"); + info.description = _("Quickly see the local time of a buddy"); -PURPLE_INIT_PLUGIN(PLUGIN_STATIC_NAME, init_plugin, info)
+PURPLE_INIT_PLUGIN(PLUGIN_STATIC_NAME, init_plugin, info); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytime/buddytime.c.old Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,260 @@
+ * Plugin Name - Summary + * 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., 51 Franklin Street, Fifth Floor, Boston, MA +#include "../common/pp_internal.h" +#define PLUGIN_ID "gtk-plugin_pack-buddytime" +#define PLUGIN_STATIC_NAME "buddytime" +#define PLUGIN_AUTHOR "Gary Kramlich <grim@reaperworld.com>" +/****************************************************************************** + *****************************************************************************/ +#define BT_NODE_SETTING "bt-timezone" +/****************************************************************************** + *****************************************************************************/ + PurpleRequestField *timezone; + PurpleConversation *conv; +/****************************************************************************** + *****************************************************************************/ +static GList *dialogs = NULL; +static GList *widgets = NULL; +/****************************************************************************** + *****************************************************************************/ +bt_widget_new(PurpleConversation *conv) { + g_return_val_if_fail(conv, NULL); + ret = g_new0(BTWidget, 1); + ret->ebox = gtk_event_box_new(); + ret->label = gtk_label_new("label"); + gtk_container_add(GTK_CONTAINER(ret->ebox), ret->label); +/****************************************************************************** + *****************************************************************************/ +bt_dialog_ok_cb(gpointer data, PurpleRequestFields *fields) { + BTDialog *dialog = (BTDialog *)data; + dialogs = g_list_remove(dialogs, dialog); +bt_dialog_cancel_cb(gpointer data, PurpleRequestFields *fields) { + BTDialog *dialog = (BTDialog *)data; + dialogs = g_list_remove(dialogs, dialog); +bt_show_dialog(PurpleBlistNode *node) { + PurpleRequestFields *fields; + PurpleRequestFieldGroup *group; + PurpleAccount *account = NULL; + dialog = g_new0(BTDialog, 1); + current = purple_blist_node_get_int(node, BT_NODE_SETTING); + /* TODO: set account from node */ + /* build the request fields */ + fields = purple_request_fields_new(); + group = purple_request_field_group_new(NULL); + purple_request_fields_add_group(fields, group); + dialog->timezone = purple_request_field_choice_new("timezone", + purple_request_field_group_add_field(group, dialog->timezone); + purple_request_field_choice_add(dialog->timezone, _("Clear setting")); + purple_request_field_choice_add(dialog->timezone, _("GMT-12")); + purple_request_field_choice_add(dialog->timezone, _("GMT-11")); + purple_request_field_choice_add(dialog->timezone, _("GMT-10")); + purple_request_field_choice_add(dialog->timezone, _("GMT-9")); + purple_request_field_choice_add(dialog->timezone, _("GMT-8")); + purple_request_field_choice_add(dialog->timezone, _("GMT-7")); + purple_request_field_choice_add(dialog->timezone, _("GMT-6")); + purple_request_field_choice_add(dialog->timezone, _("GMT-5")); + purple_request_field_choice_add(dialog->timezone, _("GMT-4")); + purple_request_field_choice_add(dialog->timezone, _("GMT-3")); + purple_request_field_choice_add(dialog->timezone, _("GMT-2")); + purple_request_field_choice_add(dialog->timezone, _("GMT-1")); + purple_request_field_choice_add(dialog->timezone, _("GMT")); + purple_request_field_choice_add(dialog->timezone, _("GMT+1")); + purple_request_field_choice_add(dialog->timezone, _("GMT+2")); + purple_request_field_choice_add(dialog->timezone, _("GMT+3")); + purple_request_field_choice_add(dialog->timezone, _("GMT+4")); + purple_request_field_choice_add(dialog->timezone, _("GMT+5")); + purple_request_field_choice_add(dialog->timezone, _("GMT+6")); + purple_request_field_choice_add(dialog->timezone, _("GMT+7")); + purple_request_field_choice_add(dialog->timezone, _("GMT+8")); + purple_request_field_choice_add(dialog->timezone, _("GMT+9")); + purple_request_field_choice_add(dialog->timezone, _("GMT+10")); + purple_request_field_choice_add(dialog->timezone, _("GMT+11")); + purple_request_field_choice_add(dialog->timezone, _("GMT+12")); +// purple_request_field_choice_set_default_value(dialog->timezone, current); +// purple_request_field_coice_set_value(dialog->timezone, current); + /* TODO: set who from blist node */ + purple_request_fields(NULL, _("Select timezone"), + _("OK"), PURPLE_CALLBACK(bt_dialog_ok_cb), + _("Cancel"), PURPLE_CALLBACK(bt_dialog_cancel_cb), + account, NULL /* who */, NULL, dialog); + dialogs = g_list_append(dialogs, dialog); +bt_edit_timezone_cb(PurpleBlistNode *node, gpointer data) { +bt_blist_drawing_menu_cb(PurpleBlistNode *node, GList **menu) { + PurpleMenuAction *action; + if (purple_blist_node_get_flags(node) & PURPLE_BLIST_NODE_FLAG_NO_SAVE) + /* ignore chats and groups */ + if(PURPLE_BLIST_NODE_IS_CHAT(node) || PURPLE_BLIST_NODE_IS_GROUP(node)) + action = purple_menu_action_new(_("Timezone"), + PURPLE_CALLBACK(bt_edit_timezone_cb), + (*menu) = g_list_append(*menu, action); +/****************************************************************************** + *****************************************************************************/ +/****************************************************************************** + *****************************************************************************/ +plugin_load(PurplePlugin *plugin) { + purple_signal_connect(purple_blist_get_handle(), + "blist-node-extended-menu", + PURPLE_CALLBACK(bt_blist_drawing_menu_cb), +plugin_unload(PurplePlugin *plugin) { +static PurplePluginInfo info = { + PURPLE_PLUGIN_MAGIC, /* Magic */ + PURPLE_MAJOR_VERSION, /* Purple Major Version */ + PURPLE_MINOR_VERSION, /* Purple Minor Version */ + PURPLE_PLUGIN_STANDARD, /* plugin type */ + PIDGIN_PLUGIN_TYPE, /* ui requirement */ + NULL, /* dependencies */ + PURPLE_PRIORITY_DEFAULT, /* priority */ + PLUGIN_ID, /* plugin id */ + PP_VERSION, /* version */ + NULL, /* description */ + PLUGIN_AUTHOR, /* author */ + PP_WEBSITE, /* website */ + plugin_load, /* load */ + plugin_unload, /* unload */ +init_plugin(PurplePlugin *plugin) { + bindtextdomain(GETTEXT_PACKAGE, PP_LOCALEDIR); + bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); + info.name = _("Buddy Time"); + info.summary = _("summary"); + info.description = _("description"); +PURPLE_INIT_PLUGIN(PLUGIN_STATIC_NAME, init_plugin, info) --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytime/buddytime.h Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,52 @@
+ * Buddy Time - Displays a buddy's local time + * A libpurple plugin that allows you to configure a timezone on a per-contact + * basis so it can display the localtime of your contact when a conversation + * starts. Convenient if you deal with contacts from many parts of the + * Copyright (C) 2006-2007, Richard Laager <rlaager@users.sf.net> + * Copyright (C) 2006, Martijn van Oosterhout <kleptog@svana.org> + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +#define CORE_PLUGIN_STATIC_NAME "buddytime" +#define CORE_PLUGIN_ID "core-kleptog-" CORE_PLUGIN_STATIC_NAME +#define PLUGIN_AUTHOR "Martijn van Oosterhout <kleptog@svana.org>" \ + "\n\t\t\tRichard Laager <rlaager@pidgin.im>" +#define BUDDYTIME_BUDDY_GET_TIMEZONE "buddy_get_timezone" +#define BUDDYTIME_TIMEZONE_GET_TIME "timezone_get_time" +typedef struct _BuddyTimeUiOps BuddyTimeUiOps; + void *(*create_menu)(const char *selected); /**< Creates a timezone menu. */ + const char * (*get_timezone_menu_selection)(void *ui_data); /**< Retrieves the menu setting. */ + void (*_buddytime_reserved1)(void); + void (*_buddytime_reserved2)(void); + void (*_buddytime_reserved3)(void); + void (*_buddytime_reserved4)(void); +#endif /* _BUDDYTIME_H_ */ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytime/gtkbuddytime.c Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,150 @@
+ * Buddy Time - Displays a buddy's local time + * A libpurple plugin that allows you to configure a timezone on a per-contact + * basis so it can display the localtime of your contact when a conversation + * starts. Convenient if you deal with contacts from many parts of the + * Copyright (C) 2006-2007, Richard Laager <rlaager@pidgin.im> + * Copyright (C) 2006, Martijn van Oosterhout <kleptog@svana.org> + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +/* If you can't figure out what this line is for, DON'T TOUCH IT. */ +#include "../common/pp_internal.h" +#define PLUGIN_STATIC_NAME "gtkbuddytime" +#define PLUGIN_ID PIDGIN_UI "-buddytime" +PurplePlugin *core_plugin = NULL; +buddytimezone_tooltip_cb(PurpleBlistNode * node, char **text, gboolean full, void *data) + timezone = purple_plugin_ipc_call(core_plugin, BUDDYTIME_BUDDY_GET_TIMEZONE, + ret = GPOINTER_TO_INT(purple_plugin_ipc_call(core_plugin, BUDDYTIME_TIMEZONE_GET_TIME, + NULL, timezone, &tm, &diff)); + newtext = g_strdup_printf("%s\n<b>Timezone:</b> %s (error)", *text, timezone); + const char *timetext = purple_time_format(&tm); + newtext = g_strdup_printf(dngettext(GETTEXT_PACKAGE, + "%s\n<b>Local Time:</b> %s (%.4g hour behind)", + "%s\n<b>Local Time:</b> %s (%.4g hours behind)", diff), + *text, timetext, diff); + newtext = g_strdup_printf(dngettext(GETTEXT_PACKAGE, + "%s\n<b>Local Time:</b> %s (%.4g hour ahead)", + "%s\n<b>Local Time:</b> %s (%.4g hours ahead)", diff), + *text, timetext, diff); } +plugin_load(PurplePlugin * plugin) + purple_signal_connect(pidgin_blist_get_handle(), "drawing-tooltip", plugin, + PURPLE_CALLBACK(buddytimezone_tooltip_cb), NULL); + core_plugin = purple_plugins_find_with_id(CORE_PLUGIN_ID); + return (core_plugin != NULL); +static PurplePluginInfo info = + PURPLE_PLUGIN_STANDARD, /**< type */ + PIDGIN_PLUGIN_TYPE, /**< ui_requirement */ + PURPLE_PLUGIN_FLAG_INVISIBLE, /**< flags */ + NULL, /**< dependencies */ + PURPLE_PRIORITY_DEFAULT, /**< priority */ + PP_VERSION, /**< version */ + NULL, /**< description */ + PLUGIN_AUTHOR, /**< author */ + PP_WEBSITE, /**< homepage */ + plugin_load, /**< load */ + NULL, /**< extra_info */ + NULL, /**< prefs_info */ + NULL, /**< reserved 1 */ + NULL, /**< reserved 2 */ + NULL, /**< reserved 3 */ + NULL /**< reserved 4 */ +init_plugin(PurplePlugin * plugin) + info.dependencies = g_list_append(info.dependencies, CORE_PLUGIN_ID); + bindtextdomain(GETTEXT_PACKAGE, PP_LOCALEDIR); + bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); + info.name = _("Buddy Time (Pidgin UI)"); + info.summary = _("Pidgin user interface for the Buddy Time plugin."); + info.description = _("Pidgin user interface for the Buddy Time plugin."); +PURPLE_INIT_PLUGIN(PLUGIN_STATIC_NAME, init_plugin, info); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytime/gtktimezone.c Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,237 @@
+/************************************************************************* + * GTK Timezone widget module + * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006 + * Licenced under the GNU General Public Licence version 2. + * This module creates the GTK widget used to select timezones. It's here to + * clearly seperate the GTK stuff from the plugin itself. + *************************************************************************/ +#define DISABLED_STRING "<Disabled>" +#define DEFAULT_STRING "<Default>" +#define MORE_STRING "More..." + struct nodestate stack[4]; +static inline const char * +menuitem_get_label(GtkMenuItem * menuitem) + return gtk_label_get_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN(menuitem)))); +menu_get_first_menuitem(GtkWidget * menu) + GList *list = gtk_container_get_children(GTK_CONTAINER(menu)); + GtkMenuItem *selection = GTK_MENU_ITEM(g_list_nth_data(list, 0)); +menu_select_cb(GtkMenuItem * menuitem, GtkWidget * menu) + const char *label = menuitem_get_label(menuitem); + if(strcmp(label, DEFAULT_STRING) == 0) + else if(strcmp(label, DISABLED_STRING) == 0) + selection = GTK_WIDGET(menu_get_first_menuitem(menu)); + gtk_widget_hide(selection); + char *str = g_strdup(label); + GtkMenuItem *parentitem; + parent = gtk_widget_get_parent(GTK_WIDGET(menuitem)); + parentitem = GTK_MENU_ITEM(gtk_menu_get_attach_widget(GTK_MENU(parent))); + label2 = menuitem_get_label(parentitem); + if(strcmp(label2, MORE_STRING) != 0) + temp = g_strconcat(label2, "/", str, NULL); + GtkMenuItem *selection = menu_get_first_menuitem(menu); + GtkOptionMenu *optionmenu = GTK_OPTION_MENU(gtk_menu_get_attach_widget(GTK_MENU(menu))); + label = GTK_LABEL(gtk_bin_get_child(GTK_BIN(selection))); + gtk_label_set_text(label, str); + gtk_widget_show(GTK_WIDGET(selection)); + gtk_option_menu_set_history(optionmenu, 0); + printf("optionmenu=%p, menu=%p, menuitem=%p, label=%p\n", optionmenu, menu, selection, +make_menu_cb(char *path, struct state *state) + /* Here we ignore strings not beginning with uppercase, since they are auxilliary files, not timezones */ + elements = g_strsplit(path, "/", 4); + for (i = 0; i < state->currdepth && state->stack[i].string; i++) + if(strcmp(elements[i], state->stack[i].string) != 0) + /* i is now the index of the first non-matching element, so free the rest */ + for (j = i; j < state->currdepth; j++) + g_free(state->stack[j].string); + GtkWidget *parent = (i == 0) ? state->base : state->stack[i - 1].submenu; + if(i == 0 && elements[1] == NULL) + menuitem = gtk_menu_item_new_with_label(elements[i]); + gtk_menu_append(parent, menuitem); + if(elements[i + 1] != NULL) /* Has submenu */ + state->stack[i].submenu = gtk_menu_new(); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), state->stack[i].submenu); + state->stack[i].string = g_strdup(elements[i]); + g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(menu_select_cb), +make_timezone_menu(const char *selected) + GtkWidget *optionmenu, *menuitem, *selection; + menuitem = gtk_menu_item_new_with_label(selected); + gtk_menu_append(menu, menuitem); + menuitem = gtk_menu_item_new_with_label(DISABLED_STRING); + g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(menu_select_cb), menu); + gtk_menu_append(menu, menuitem); + menuitem = gtk_menu_item_new_with_label(DEFAULT_STRING); + g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(menu_select_cb), menu); + gtk_menu_append(menu, menuitem); + menuitem = gtk_menu_item_new_with_label(MORE_STRING); + gtk_menu_append(menu, menuitem); + state.extra = gtk_menu_new(); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), state.extra); + recurse_directory("/usr/share/zoneinfo", (DirRecurseMatch) make_menu_cb, &state); + for (i = 0; i < state.currdepth; i++) + g_free(state.stack[i].string); + optionmenu = gtk_option_menu_new(); + gtk_option_menu_set_menu(GTK_OPTION_MENU(optionmenu), menu); + gtk_widget_show_all(optionmenu); + if(strcmp(selected, "") == 0) + gtk_option_menu_set_history(GTK_OPTION_MENU(optionmenu), 2); + gtk_widget_hide(selection); + else if(strcmp(selected, "none") == 0) + gtk_option_menu_set_history(GTK_OPTION_MENU(optionmenu), 1); + gtk_widget_hide(selection); + gtk_option_menu_set_history(GTK_OPTION_MENU(optionmenu), 0); +get_timezone_menu_selection(void *widget) + GtkOptionMenu *menu = GTK_OPTION_MENU(widget); + int sel = gtk_option_menu_get_history(menu); + if(sel == 2) /* Default */ + if(sel == 1) /* Disabled */ + GtkLabel *l = GTK_LABEL(gtk_bin_get_child(GTK_BIN(menu))); + return gtk_label_get_text(l); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytime/gtktimezonetest.c Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,343 @@
+/************************************************************************* + * GTK Timezone test program + * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006 + * Licenced under the GNU General Public Licence version 2. + * A test program to play with different ways that user could select from + * the huge list of timezones. Eventually things tested here should migrate + * to the module itself. + *************************************************************************/ +#define PACKAGE "Hello World" +#define DISABLED_STRING "<Disabled>" +#define DEFAULT_STRING "<Default>" +#define MORE_STRING "More..." + * Terminate the main loop. +on_destroy(GtkWidget * widget, gpointer data) + struct nodestate stack[4]; +static inline const char * +menuitem_get_label(GtkMenuItem * menuitem) + return gtk_label_get_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN(menuitem)))); +menu_get_first_menuitem(GtkMenu * menu) + GList *list = gtk_container_get_children(GTK_CONTAINER(menu)); + GtkWidget *selection = GTK_WIDGET(g_list_nth_data(list, 0)); +menu_select_cb(GtkMenuItem * menuitem, GtkWidget * menu) + const char *label = menuitem_get_label(menuitem); +// printf( "menuitem = %s(%p), menu = %s(%p)\n", G_OBJECT_TYPE_NAME(menuitem), menuitem, G_OBJECT_TYPE_NAME(menu), menu ); + if(strcmp(label, DEFAULT_STRING) == 0) + else if(strcmp(label, DISABLED_STRING) == 0) + selection = menu_get_first_menuitem(GTK_MENU(menu)); + gtk_widget_hide(selection); + char *str = g_strdup(label); + GtkMenuItem *parentitem; + parent = gtk_widget_get_parent(GTK_WIDGET(menuitem)); +// printf( "parent = %s(%p)\n", G_OBJECT_TYPE_NAME(parent), parent); + parentitem = GTK_MENU_ITEM(gtk_menu_get_attach_widget(GTK_MENU(parent))); +// printf( "parentitem = %s(%p)\n", G_OBJECT_TYPE_NAME(parentitem), parentitem); + label2 = menuitem_get_label(parentitem); + if(strcmp(label2, MORE_STRING) != 0) + temp = g_strconcat(label2, "/", str, NULL); + GtkWidget *selection = menu_get_first_menuitem(GTK_MENU(menu)); + GtkOptionMenu *optionmenu = GTK_OPTION_MENU(gtk_menu_get_attach_widget(GTK_MENU(menu))); + label = GTK_LABEL(gtk_bin_get_child(GTK_BIN(selection))); + gtk_label_set_text(label, str); + gtk_widget_show(GTK_WIDGET(selection)); + gtk_option_menu_set_history(optionmenu, 0); +make_menu_cb(char *path, struct state *state) + /* Here we ignore strings not beginning with uppercase, since they are auxilliary files, not timezones */ + elements = g_strsplit(path, "/", 4); + for (i = 0; i < state->currdepth && state->stack[i].string; i++) + if(strcmp(elements[i], state->stack[i].string) != 0) + /* i is now the index of the first non-matching element, so free the rest */ + for (j = i; j < state->currdepth; j++) + g_free(state->stack[j].string); + GtkTreeIter *parent = (i == 0) ? NULL : &state->stack[i - 1].iter; + GtkWidget *parent = (i == 0) ? state->base : state->stack[i - 1].submenu; + if(i == 0 && elements[1] == NULL) + gtk_tree_store_append(state->store, &state->stack[i].iter, parent); + gtk_tree_store_set(state->store, &state->stack[i].iter, STRING_COLUMN, elements[i], -1); + state->stack[i].string = g_strdup(elements[i]); + menuitem = gtk_menu_item_new_with_label(elements[i]); + gtk_menu_append(parent, menuitem); + if(elements[i + 1] != NULL) /* Has submenu */ + state->stack[i].submenu = gtk_menu_new(); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), state->stack[i].submenu); + state->stack[i].string = g_strdup(elements[i]); + g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(menu_select_cb), +make_menu2(char *selected) + GtkTreeStore *store = gtk_tree_store_new(N_COLUMNS, /* Total number of columns */ + G_TYPE_STRING); /* Timezone */ + GtkCellRenderer *renderer; + GtkTreeIter iter1, iter2; + gtk_tree_store_append(store, &iter1, NULL); /* Acquire an iterator */ + gtk_tree_store_set(store, &iter1, STRING_COLUMN, DISABLED_STRING, -1); + gtk_tree_store_append(store, &iter1, NULL); /* Acquire an iterator */ + gtk_tree_store_set(store, &iter1, STRING_COLUMN, DEFAULT_STRING, -1); + gtk_tree_store_append(store, &iter2, &iter1); + gtk_tree_store_set(store, &iter1, STRING_COLUMN, MORE_STRING, -1); + GtkWidget *optionmenu, *menuitem, *selection; + menuitem = gtk_menu_item_new_with_label(selected); + gtk_menu_append(menu, menuitem); + menuitem = gtk_menu_item_new_with_label(DISABLED_STRING); + g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(menu_select_cb), menu); + gtk_menu_append(menu, menuitem); + menuitem = gtk_menu_item_new_with_label(DEFAULT_STRING); + g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(menu_select_cb), menu); + gtk_menu_append(menu, menuitem); + menuitem = gtk_menu_item_new_with_label(MORE_STRING); + gtk_menu_append(menu, menuitem); + state.extra = gtk_menu_new(); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), state.extra); + recurse_directory("/usr/share/zoneinfo", (DirRecurseMatch) make_menu_cb, &state); + for (i = 0; i < state.currdepth; i++) + g_free(state.stack[i].string); + tree = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store)); + renderer = gtk_cell_renderer_text_new(); + gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(tree), renderer, TRUE); + gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(tree), renderer, "text", 0, NULL); + gtk_widget_show_all(tree); + optionmenu = gtk_option_menu_new(); + gtk_option_menu_set_menu(GTK_OPTION_MENU(optionmenu), menu); + gtk_widget_show_all(optionmenu); + if(strcmp(selected, "") == 0) + gtk_option_menu_set_history(GTK_OPTION_MENU(optionmenu), 2); + gtk_widget_hide(selection); + else if(strcmp(selected, "none") == 0) + gtk_option_menu_set_history(GTK_OPTION_MENU(optionmenu), 1); + gtk_widget_hide(selection); + gtk_option_menu_set_history(GTK_OPTION_MENU(optionmenu), 0); +main(int argc, char *argv[]) + gtk_init(&argc, &argv); + /* create the main, top level, window */ + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + /* give the window a 20px wide border */ + gtk_container_set_border_width(GTK_CONTAINER(window), 20); + /* give it the title */ + gtk_window_set_title(GTK_WINDOW(window), PACKAGE " " VERSION); + /* open it a bit wider so that both the label and title show up */ + gtk_window_set_default_size(GTK_WINDOW(window), 200, 50); + /* Connect the destroy event of the window with our on_destroy function + * When the window is about to be destroyed we get a notificaiton and + * stop the main GTK loop + g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(on_destroy), NULL); + /* Create the "Hello, World" label */ + label = gtk_label_new("Select a timezone:"); + gtk_widget_show(label); + frame = gtk_vbox_new(FALSE, 0); + gtk_widget_show(frame); + /* and insert it into the main window */ + gtk_container_add(GTK_CONTAINER(window), frame); + gtk_container_add(GTK_CONTAINER(frame), label); + menu = make_menu2("none"); + gtk_container_add(GTK_CONTAINER(frame), menu); + /* make sure that everything, window and label, are visible */ + gtk_widget_show(window); + /* start the main loop */ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytime/localtime.c Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,1250 @@
+/************************************************************************* + * copied from the Olson Timezone code, licence unchanged. + * by Martijn van Oosterhout <kleptog@svana.org> April 2006 + * Original Licence below (public domain). + * This code has been copied from the Olson Timezone code, but heavily + * adapted to meet my needs. In particular, you can load multiple timezones + * and specify which timezone to convert with. + *************************************************************************/ +** This file is in the public domain, so clarified as of +** 1996-06-05 by Arthur David Olson. +#define TM_GMTOFF tm_gmtoff +** Leap second handling from Bradley White. +** POSIX-style TZ environment variable handling from Guy Harris. +#include "float.h" /* for FLT_MAX and DBL_MAX */ +#define TZ_ABBR_MAX_LEN 16 +#endif /* !defined TZ_ABBR_MAX_LEN */ +#ifndef TZ_ABBR_CHAR_SET +#define TZ_ABBR_CHAR_SET \ + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 :+-._" +#endif /* !defined TZ_ABBR_CHAR_SET */ +#ifndef TZ_ABBR_ERR_CHAR +#define TZ_ABBR_ERR_CHAR '_' +#endif /* !defined TZ_ABBR_ERR_CHAR */ +** SunOS 4.1.1 headers lack O_BINARY. +#define OPEN_MODE (O_RDONLY | O_BINARY) +#endif /* defined O_BINARY */ +#define OPEN_MODE O_RDONLY +#endif /* !defined O_BINARY */ +** Someone might make incorrect use of a time zone abbreviation: +** 1. They might reference tzname[0] before calling tzset (explicitly +** 2. They might reference tzname[1] before calling tzset (explicitly +** 3. They might reference tzname[1] after setting to a time zone +** in which Daylight Saving Time is never observed. +** 4. They might reference tzname[0] after setting to a time zone +** in which Standard Time is never observed. +** 5. They might reference tm.TM_ZONE after calling offtime. +** What's best to do in the above cases is open to debate; +** for now, we just set things up so that in any of the five cases +** WILDABBR is used. Another possibility: initialize tzname[0] to the +** string "tzname[0] used before set", and similarly for the other cases. +** And another: initialize tzname[0] to "ERA", with an explanation in the +** manual page of what this "time zone abbreviation" means (doing this so +** that tzname[0] has the "normal" length of three characters). +#endif /* !defined WILDABBR */ +static char wildabbr[] = WILDABBR; +static const char gmt[] = "GMT"; +** The DST rules to use if TZ has no rules and we can't load TZDEFRULES. +** We default to US rules as of 1999-08-17. +** POSIX 1003.1 section 8.1.1 says that the default DST rules are +** implementation dependent; for historical reasons, US rules are a +#define TZDEFRULESTRING ",M4.1.0,M10.5.0" +#endif /* !defined TZDEFDST */ +struct ttinfo { /* time type information */ + long tt_gmtoff; /* UTC offset in seconds */ + int tt_isdst; /* used to set tm_isdst */ + int tt_abbrind; /* abbreviation list index */ + int tt_ttisstd; /* TRUE if transition is std time */ + int tt_ttisgmt; /* TRUE if transition is UTC */ +struct lsinfo { /* leap second information */ + time_t ls_trans; /* transition time */ + long ls_corr; /* correction to apply */ +#define BIGGEST(a, b) (((a) > (b)) ? (a) : (b)) +#define MY_TZNAME_MAX TZNAME_MAX +#endif /* defined TZNAME_MAX */ +#define MY_TZNAME_MAX 255 +#endif /* !defined TZNAME_MAX */ + time_t ats[TZ_MAX_TIMES]; + unsigned char types[TZ_MAX_TIMES]; + struct ttinfo ttis[TZ_MAX_TYPES]; + char chars[BIGGEST(BIGGEST(TZ_MAX_CHARS + 1, sizeof gmt), + (2 * (MY_TZNAME_MAX + 1)))]; + struct lsinfo lsis[TZ_MAX_LEAPS]; + int r_type; /* type of rule--see below */ + int r_day; /* day number of rule */ + int r_week; /* week number of rule */ + int r_mon; /* month number of rule */ + long r_time; /* transition time of rule */ +#define JULIAN_DAY 0 /* Jn - Julian day */ +#define DAY_OF_YEAR 1 /* n - day of year */ +#define MONTH_NTH_DAY_OF_WEEK 2 /* Mm.n.d - month, week, day of week */ +** Prototypes for static functions. +static long detzcode P((const char * codep)); +static const char * getzname P((const char * strp)); +static const char * getqzname P((const char * strp, const char delim)); +static const char * getnum P((const char * strp, int * nump, int min, +static const char * getsecs P((const char * strp, long * secsp)); +static const char * getoffset P((const char * strp, long * offsetp)); +static const char * getrule P((const char * strp, struct rule * rulep)); +static void gmtload P((struct state * sp)); +struct tm * gmtsub P((const time_t * timep, long offset, +struct tm * localsub P((const time_t * timep, long offset, + struct tm * tmp, struct state *sp)); +static int increment_overflow P((int * number, int delta)); +static int leaps_thru_end_of P((int y)); +static struct tm * timesub P((const time_t * timep, long offset, + const struct state * sp, struct tm * tmp)); +static time_t transtime P((time_t janfirst, int year, + const struct rule * rulep, long offset)); +static int tzload P((const char * name, struct state * sp)); +static int tzparse P((const char * name, struct state * sp, +struct state *timezone_load P((const char * name)); +static struct state * gmtptr; +#endif /* defined ALL_STATE */ +static struct state gmtmem; +#define gmtptr (&gmtmem) +#define TZ_STRLEN_MAX 255 +#endif /* !defined TZ_STRLEN_MAX */ +//static char lcl_TZname[TZ_STRLEN_MAX + 1]; +//static int lcl_is_set; +** Section 4.12.3 of X3.159-1989 requires that +** Except for the strftime function, these functions [asctime, +** ctime, gmtime, localtime] return values in one of two static +** objects: a broken-down time structure and an array of char. +** Thanks to Paul Eggert for noting this. +#endif /* defined USG_COMPAT */ +#endif /* defined ALTZONE */ +const char * const codep; + result = (codep[0] & 0x80) ? ~0L : 0L; + for (i = 0; i < 4; ++i) + result = (result << 8) | (codep[i] & 0xff); +register const char * name; +register struct state * const sp; + register const char * p; + if (name == NULL && (name = TZDEFAULT) == NULL) + ** Section 4.9.1 of the C standard says that + ** "FILENAME_MAX expands to an integral constant expression + ** that is the size needed for an array of char large enough + ** to hold the longest file name string that the implementation + ** guarantees can be opened." + char fullname[FILENAME_MAX + 1]; + doaccess = name[0] == '/'; + if ((p = tzdir) == NULL) + if ((strlen(p) + strlen(name) + 1) >= sizeof fullname) + (void) strcpy(fullname, p); + (void) strcat(fullname, "/"); + (void) strcat(fullname, name); + ** Set doaccess if '.' (as in "../") shows up in name. + if (strchr(name, '.') != NULL) + if (doaccess && access(name, R_OK) != 0) + if ((fid = open(name, OPEN_MODE)) == -1) + char buf[sizeof *sp + sizeof *tzhp]; + i = read(fid, u.buf, sizeof u.buf); + ttisstdcnt = (int) detzcode(u.tzhead.tzh_ttisstdcnt); + ttisgmtcnt = (int) detzcode(u.tzhead.tzh_ttisgmtcnt); + sp->leapcnt = (int) detzcode(u.tzhead.tzh_leapcnt); + sp->timecnt = (int) detzcode(u.tzhead.tzh_timecnt); + sp->typecnt = (int) detzcode(u.tzhead.tzh_typecnt); + sp->charcnt = (int) detzcode(u.tzhead.tzh_charcnt); + p = u.tzhead.tzh_charcnt + sizeof u.tzhead.tzh_charcnt; + if (sp->leapcnt < 0 || sp->leapcnt > TZ_MAX_LEAPS || + sp->typecnt <= 0 || sp->typecnt > TZ_MAX_TYPES || + sp->timecnt < 0 || sp->timecnt > TZ_MAX_TIMES || + sp->charcnt < 0 || sp->charcnt > TZ_MAX_CHARS || + (ttisstdcnt != sp->typecnt && ttisstdcnt != 0) || + (ttisgmtcnt != sp->typecnt && ttisgmtcnt != 0)) + if (i - (p - u.buf) < sp->timecnt * 4 + /* ats */ + sp->timecnt + /* types */ + sp->typecnt * (4 + 2) + /* ttinfos */ + sp->charcnt + /* chars */ + sp->leapcnt * (4 + 4) + /* lsinfos */ + ttisstdcnt + /* ttisstds */ + ttisgmtcnt) /* ttisgmts */ + for (i = 0; i < sp->timecnt; ++i) { + sp->ats[i] = detzcode(p); + for (i = 0; i < sp->timecnt; ++i) { + sp->types[i] = (unsigned char) *p++; + if (sp->types[i] >= sp->typecnt) + for (i = 0; i < sp->typecnt; ++i) { + register struct ttinfo * ttisp; + ttisp->tt_gmtoff = detzcode(p); + ttisp->tt_isdst = (unsigned char) *p++; + if (ttisp->tt_isdst != 0 && ttisp->tt_isdst != 1) + ttisp->tt_abbrind = (unsigned char) *p++; + if (ttisp->tt_abbrind < 0 || + ttisp->tt_abbrind > sp->charcnt) + for (i = 0; i < sp->charcnt; ++i) + sp->chars[i] = '\0'; /* ensure '\0' at end */ + for (i = 0; i < sp->leapcnt; ++i) { + register struct lsinfo * lsisp; + lsisp->ls_trans = detzcode(p); + lsisp->ls_corr = detzcode(p); + for (i = 0; i < sp->typecnt; ++i) { + register struct ttinfo * ttisp; + ttisp->tt_ttisstd = FALSE; + ttisp->tt_ttisstd = *p++; + if (ttisp->tt_ttisstd != TRUE && + ttisp->tt_ttisstd != FALSE) + for (i = 0; i < sp->typecnt; ++i) { + register struct ttinfo * ttisp; + ttisp->tt_ttisgmt = FALSE; + ttisp->tt_ttisgmt = *p++; + if (ttisp->tt_ttisgmt != TRUE && + ttisp->tt_ttisgmt != FALSE) + ** Out-of-sort ats should mean we're running on a + ** signed time_t system but using a data file with + ** unsigned values (or vice versa). + for (i = 0; i < sp->timecnt - 2; ++i) + if (sp->ats[i] > sp->ats[i + 1]) { + if (TYPE_SIGNED(time_t)) { + ** Ignore the end (easy). + ** Ignore the beginning (harder). + for (j = 0; j + i < sp->timecnt; ++j) { + sp->ats[j] = sp->ats[j + i]; + sp->types[j] = sp->types[j + i]; +struct state *timezone_load(name) + struct state *sp = malloc( sizeof(struct state) ); + res = tzload( name, sp ); +static const int mon_lengths[2][MONSPERYEAR] = { + { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, + { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } +static const int year_lengths[2] = { + DAYSPERNYEAR, DAYSPERLYEAR +** Given a pointer into a time zone string, scan until a character that is not +** a valid character in a zone name is found. Return a pointer to that +register const char * strp; + while ((c = *strp) != '\0' && !is_digit(c) && c != ',' && c != '-' && +** Given a pointer into an extended time zone string, scan until the ending +** delimiter of the zone name is located. Return a pointer to the delimiter. +** As with getzname above, the legal character set is actually quite +** restricted, with other characters producing undefined results. +** We choose not to care - allowing almost anything to be in the zone abbrev. +getqzname(register const char *strp, const char delim) +register const char * strp; + while ((c = *strp) != '\0' && c != delim) +** Given a pointer into a time zone string, extract a number from that string. +** Check that the number is within a specified range; if it is not, return +** Otherwise, return a pointer to the first character not part of the number. +getnum(strp, nump, min, max) +register const char * strp; + if (strp == NULL || !is_digit(c = *strp)) + num = num * 10 + (c - '0'); + return NULL; /* illegal value */ + return NULL; /* illegal value */ +** Given a pointer into a time zone string, extract a number of seconds, +** in hh[:mm[:ss]] form, from the string. +** If any error occurs, return NULL. +** Otherwise, return a pointer to the first character not part of the number +register const char * strp; + ** `HOURSPERDAY * DAYSPERWEEK - 1' allows quasi-Posix rules like + ** "M10.4.6/26", which does not conform to Posix, + ** but which specifies the equivalent of + ** ``02:00 on the first Sunday on or after 23 Oct''. + strp = getnum(strp, &num, 0, HOURSPERDAY * DAYSPERWEEK - 1); + *secsp = num * (long) SECSPERHOUR; + strp = getnum(strp, &num, 0, MINSPERHOUR - 1); + *secsp += num * SECSPERMIN; + /* `SECSPERMIN' allows for leap seconds. */ + strp = getnum(strp, &num, 0, SECSPERMIN); +** Given a pointer into a time zone string, extract an offset, in +** [+-]hh[:mm[:ss]] form, from the string. +** If any error occurs, return NULL. +** Otherwise, return a pointer to the first character not part of the time. +getoffset(strp, offsetp) +register const char * strp; + } else if (*strp == '+') + strp = getsecs(strp, offsetp); + return NULL; /* illegal time */ +** Given a pointer into a time zone string, extract a rule in the form +** date[/time]. See POSIX section 8 for the format of "date" and "time". +** If a valid rule is not found, return NULL. +** Otherwise, return a pointer to the first character not part of the rule. +register struct rule * const rulep; + rulep->r_type = JULIAN_DAY; + strp = getnum(strp, &rulep->r_day, 1, DAYSPERNYEAR); + } else if (*strp == 'M') { + rulep->r_type = MONTH_NTH_DAY_OF_WEEK; + strp = getnum(strp, &rulep->r_mon, 1, MONSPERYEAR); + strp = getnum(strp, &rulep->r_week, 1, 5); + strp = getnum(strp, &rulep->r_day, 0, DAYSPERWEEK - 1); + } else if (is_digit(*strp)) { + rulep->r_type = DAY_OF_YEAR; + strp = getnum(strp, &rulep->r_day, 0, DAYSPERLYEAR - 1); + } else return NULL; /* invalid format */ + strp = getsecs(strp, &rulep->r_time); + } else rulep->r_time = 2 * SECSPERHOUR; /* default = 2:00:00 */ +** Given the Epoch-relative time of January 1, 00:00:00 UTC, in a year, the +** year, a rule, and the offset from UTC at the time that rule takes effect, +** calculate the Epoch-relative time that rule takes effect. +transtime(janfirst, year, rulep, offset) +register const struct rule * const rulep; + int d, m1, yy0, yy1, yy2, dow; + leapyear = isleap(year); + switch (rulep->r_type) { + ** Jn - Julian day, 1 == January 1, 60 == March 1 even in leap + ** In non-leap years, or if the day number is 59 or less, just + ** add SECSPERDAY times the day number-1 to the time of + ** January 1, midnight, to get the day. + value = janfirst + (rulep->r_day - 1) * SECSPERDAY; + if (leapyear && rulep->r_day >= 60) + ** Just add SECSPERDAY times the day number to the time of + ** January 1, midnight, to get the day. + value = janfirst + rulep->r_day * SECSPERDAY; + case MONTH_NTH_DAY_OF_WEEK: + ** Mm.n.d - nth "dth day" of month m. + for (i = 0; i < rulep->r_mon - 1; ++i) + value += mon_lengths[leapyear][i] * SECSPERDAY; + ** Use Zeller's Congruence to get day-of-week of first day of + m1 = (rulep->r_mon + 9) % 12 + 1; + yy0 = (rulep->r_mon <= 2) ? (year - 1) : year; + dow = ((26 * m1 - 2) / 10 + + 1 + yy2 + yy2 / 4 + yy1 / 4 - 2 * yy1) % 7; + ** "dow" is the day-of-week of the first day of the month. Get + ** the day-of-month (zero-origin) of the first "dow" day of the + d = rulep->r_day - dow; + for (i = 1; i < rulep->r_week; ++i) { + mon_lengths[leapyear][rulep->r_mon - 1]) + ** "d" is the day-of-month (zero-origin) of the day we want. + value += d * SECSPERDAY; + ** "value" is the Epoch-relative time of 00:00:00 UTC on the day in + ** question. To get the Epoch-relative time of the specified local + ** time on that day, add the transition time and the current offset + return value + rulep->r_time + offset; +** Given a POSIX section 8-style TZ string, fill in the rule tables as +tzparse(name, sp, lastditch) +register struct state * const sp; + register unsigned char * typep; + register int load_result; + stdlen = strlen(name); /* length of standard zone name */ + if (stdlen >= sizeof sp->chars) + stdlen = (sizeof sp->chars) - 1; + name = getqzname(name, '>'); + stdlen = name - stdname; + stdlen = name - stdname; + name = getoffset(name, &stdoffset); + load_result = tzload(TZDEFRULES, sp); + sp->leapcnt = 0; /* so, we're off a little */ + name = getqzname(name, '>'); + dstlen = name - dstname; + dstlen = name - dstname; /* length of DST zone name */ + if (*name != '\0' && *name != ',' && *name != ';') { + name = getoffset(name, &dstoffset); + } else dstoffset = stdoffset - SECSPERHOUR; + if (*name == '\0' && load_result != 0) + name = TZDEFRULESTRING; + if (*name == ',' || *name == ';') { + register time_t janfirst; + if ((name = getrule(name, &start)) == NULL) + if ((name = getrule(name, &end)) == NULL) + sp->typecnt = 2; /* standard time and DST */ + ** Two transitions per year, from EPOCH_YEAR to 2037. + sp->timecnt = 2 * (2037 - EPOCH_YEAR + 1); + if (sp->timecnt > TZ_MAX_TIMES) + sp->ttis[0].tt_gmtoff = -dstoffset; + sp->ttis[0].tt_isdst = 1; + sp->ttis[0].tt_abbrind = stdlen + 1; + sp->ttis[1].tt_gmtoff = -stdoffset; + sp->ttis[1].tt_isdst = 0; + sp->ttis[1].tt_abbrind = 0; + for (year = EPOCH_YEAR; year <= 2037; ++year) { + starttime = transtime(janfirst, year, &start, + endtime = transtime(janfirst, year, &end, + if (starttime > endtime) { + *typep++ = 1; /* DST ends */ + *typep++ = 0; /* DST begins */ + *typep++ = 0; /* DST begins */ + *typep++ = 1; /* DST ends */ + janfirst += year_lengths[isleap(year)] * + register long theirstdoffset; + register long theirdstoffset; + register long theiroffset; + ** Initial values of theirstdoffset and theirdstoffset. + for (i = 0; i < sp->timecnt; ++i) { + if (!sp->ttis[j].tt_isdst) { + -sp->ttis[j].tt_gmtoff; + for (i = 0; i < sp->timecnt; ++i) { + if (sp->ttis[j].tt_isdst) { + -sp->ttis[j].tt_gmtoff; + ** Initially we're assumed to be in standard time. + theiroffset = theirstdoffset; + ** Now juggle transition times and types + ** tracking offsets as you do. + for (i = 0; i < sp->timecnt; ++i) { + sp->types[i] = sp->ttis[j].tt_isdst; + if (sp->ttis[j].tt_ttisgmt) { + /* No adjustment to transition time */ + ** If summer time is in effect, and the + ** transition time was not specified as + ** standard time, add the summer time + ** offset to the transition time; + ** otherwise, add the standard time + ** offset to the transition time. + ** Transitions from DST to DDST + ** will effectively disappear since + ** POSIX provides for only one DST + if (isdst && !sp->ttis[j].tt_ttisstd) { + sp->ats[i] += dstoffset - + sp->ats[i] += stdoffset - + theiroffset = -sp->ttis[j].tt_gmtoff; + if (sp->ttis[j].tt_isdst) + theirdstoffset = theiroffset; + else theirstdoffset = theiroffset; + ** Finally, fill in ttis. + ** ttisstd and ttisgmt need not be handled. + sp->ttis[0].tt_gmtoff = -stdoffset; + sp->ttis[0].tt_isdst = FALSE; + sp->ttis[0].tt_abbrind = 0; + sp->ttis[1].tt_gmtoff = -dstoffset; + sp->ttis[1].tt_isdst = TRUE; + sp->ttis[1].tt_abbrind = stdlen + 1; + sp->typecnt = 1; /* only standard time */ + sp->ttis[0].tt_gmtoff = -stdoffset; + sp->ttis[0].tt_isdst = 0; + sp->ttis[0].tt_abbrind = 0; + sp->charcnt = stdlen + 1; + sp->charcnt += dstlen + 1; + if ((size_t) sp->charcnt > sizeof sp->chars) + (void) strncpy(cp, stdname, stdlen); + (void) strncpy(cp, dstname, dstlen); +struct state * const sp; + if (tzload(gmt, sp) != 0) + (void) tzparse(gmt, sp, TRUE); +** The easy way to behave "as if no library function calls" localtime +** is to not call it--so we drop its guts into "localsub", which can be +** freely called. (And no, the PANS doesn't require the above behavior-- +** but it *is* desirable.) +** The unused offset argument is for the benefit of mktime variants. +localsub(timep, offset, tmp, sp) +const time_t * const timep; + register const struct ttinfo * ttisp; + register struct tm * result; + const time_t t = *timep; + return gmtsub(timep, offset, tmp); +#endif /* defined ALL_STATE */ + if (sp->timecnt == 0 || t < sp->ats[0]) { + while (sp->ttis[i].tt_isdst) + if (++i >= sp->typecnt) { + for (i = 1; i < sp->timecnt; ++i) + i = (int) sp->types[i - 1]; + ** To get (wrong) behavior that's compatible with System V Release 2.0 + ** you'd replace the statement below with + ** t += ttisp->tt_gmtoff; + ** timesub(&t, 0L, sp, tmp); + result = timesub(&t, ttisp->tt_gmtoff, sp, tmp); + tmp->tm_isdst = ttisp->tt_isdst; + tzname[tmp->tm_isdst] = &sp->chars[ttisp->tt_abbrind]; + tmp->TM_ZONE = &sp->chars[ttisp->tt_abbrind]; +#endif /* defined TM_ZONE */ +** gmtsub is to gmtime as localsub is to localtime. +gmtsub(timep, offset, tmp) +const time_t * const timep; + register struct tm * result; + gmtptr = (struct state *) malloc(sizeof *gmtptr); +#endif /* defined ALL_STATE */ + result = timesub(timep, offset, gmtptr, tmp); + ** Could get fancy here and deliver something such as + ** "UTC+xxxx" or "UTC-xxxx" if offset is non-zero, + ** but this is no time for a treasure hunt. + tmp->TM_ZONE = wildabbr; + else tmp->TM_ZONE = gmtptr->chars; +#endif /* defined ALL_STATE */ + tmp->TM_ZONE = gmtptr->chars; +#endif /* defined TM_ZONE */ +** Return the number of leap years through the end of the given year +** where, to make the math easy, the answer for year zero is defined as zero. + return (y >= 0) ? (y / 4 - y / 100 + y / 400) : + -(leaps_thru_end_of(-(y + 1)) + 1); +timesub(timep, offset, sp, tmp) +const time_t * const timep; +register const struct state * const sp; +register struct tm * const tmp; + register const struct lsinfo * lp; + register int idays; /* unsigned would be so 2003 */ + register const int * ip; + i = (sp == NULL) ? 0 : sp->leapcnt; +#endif /* defined ALL_STATE */ + if (*timep >= lp->ls_trans) { + if (*timep == lp->ls_trans) { + hit = ((i == 0 && lp->ls_corr > 0) || + lp->ls_corr > sp->lsis[i - 1].ls_corr); + sp->lsis[i].ls_trans == + sp->lsis[i - 1].ls_trans + 1 && + sp->lsis[i - 1].ls_corr + 1) { + tdays = *timep / SECSPERDAY; + rem = *timep - tdays * SECSPERDAY; + while (tdays < 0 || tdays >= year_lengths[isleap(y)]) { + register time_t tdelta; + tdelta = tdays / DAYSPERLYEAR; + if (tdelta - idelta >= 1 || idelta - tdelta >= 1) + idelta = (tdays < 0) ? -1 : 1; + if (increment_overflow(&newy, idelta)) + leapdays = leaps_thru_end_of(newy - 1) - + leaps_thru_end_of(y - 1); + tdays -= ((time_t) newy - y) * DAYSPERNYEAR; + seconds = tdays * SECSPERDAY + 0.5; + tdays = seconds / SECSPERDAY; + rem += seconds - tdays * SECSPERDAY; + ** Given the range, we can now fearlessly cast... + while (rem >= SECSPERDAY) { + if (increment_overflow(&y, -1)) + idays += year_lengths[isleap(y)]; + while (idays >= year_lengths[isleap(y)]) { + idays -= year_lengths[isleap(y)]; + if (increment_overflow(&y, 1)) + if (increment_overflow(&tmp->tm_year, -TM_YEAR_BASE)) + ** The "extra" mods below avoid overflow problems. + tmp->tm_wday = EPOCH_WDAY + + ((y - EPOCH_YEAR) % DAYSPERWEEK) * + (DAYSPERNYEAR % DAYSPERWEEK) + + leaps_thru_end_of(y - 1) - + leaps_thru_end_of(EPOCH_YEAR - 1) + + tmp->tm_wday %= DAYSPERWEEK; + tmp->tm_wday += DAYSPERWEEK; + tmp->tm_hour = (int) (rem / SECSPERHOUR); + tmp->tm_min = (int) (rem / SECSPERMIN); + ** A positive leap second requires a special + ** representation. This uses "... ??:59:60" et seq. + tmp->tm_sec = (int) (rem % SECSPERMIN) + hit; + ip = mon_lengths[isleap(y)]; + for (tmp->tm_mon = 0; idays >= ip[tmp->tm_mon]; ++(tmp->tm_mon)) + idays -= ip[tmp->tm_mon]; + tmp->tm_mday = (int) (idays + 1); + tmp->TM_GMTOFF = offset; +#endif /* defined TM_GMTOFF */ +** Adapted from code provided by Robert Elz, who writes: +** The "best" way to do mktime I think is based on an idea of Bob +** Kridle's (so its said...) from a long time ago. +** It does a binary search of the time_t space. Since time_t's are +** just 32 bits, its a max of 32 iterations (even at 64 bits it +** would still be very reasonable). +#endif /* !defined WRONG */ +** Simplified normalize logic courtesy Paul Eggert. +increment_overflow(number, delta) + return (*number < number0) != (delta < 0); +int tz_init( const char *zoneinfo_dir ) + if( zoneinfo_dir == NULL ) + ptr = malloc( strlen(zoneinfo_dir) + 10 ); + sprintf( ptr, "%s/zone.tab", zoneinfo_dir ); + fd = open( ptr, O_RDONLY ); + tzdir = strdup(zoneinfo_dir); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytime/localtime.h Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,16 @@
+/************************************************************************* + * Header file for timezone module + * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006 + * Licenced under the GNU General Public Licence version 2. + *************************************************************************/ +struct state *timezone_load (const char * name); +struct tm * gmtsub (const time_t * timep, long offset, +struct tm * localsub (const time_t * timep, long offset, + struct tm * tmp, struct state *sp); +int tz_init(const char *zoneinfo_dir); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytime/private.h Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,316 @@
+/************************************************************************* + * Private Header file for timezone module + * copied from Olson timezone code, licence unchanged + * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006 + *************************************************************************/ +** This file is in the public domain, so clarified as of +** 1996-06-05 by Arthur David Olson. +** This header is for use ONLY with the time conversion code. +** There is no guarantee that it will remain unchanged, +** or that it will remain at all. +** Do NOT copy it to any system include directory. +#define GRANDPARENTED "Local time zone must be set--see zic manual page" +** Defaults for preprocessor symbols. +** You can override these in your C compiler options, e.g. `-DHAVE_ADJTIME=0'. +#endif /* !defined HAVE_ADJTIME */ +#endif /* !defined HAVE_GETTEXT */ +#ifndef HAVE_INCOMPATIBLE_CTIME_R +#define HAVE_INCOMPATIBLE_CTIME_R 0 +#endif /* !defined INCOMPATIBLE_CTIME_R */ +#ifndef HAVE_SETTIMEOFDAY +#define HAVE_SETTIMEOFDAY 3 +#endif /* !defined HAVE_SETTIMEOFDAY */ +#endif /* !defined HAVE_STRERROR */ +#endif /* !defined HAVE_SYMLINK */ +#define HAVE_SYS_STAT_H 1 +#endif /* !defined HAVE_SYS_STAT_H */ +#define HAVE_SYS_WAIT_H 1 +#endif /* !defined HAVE_SYS_WAIT_H */ +#endif /* !defined HAVE_UNISTD_H */ +#endif /* !defined HAVE_UTMPX_H */ +#define LOCALE_HOME "/usr/lib/locale" +#endif /* !defined LOCALE_HOME */ +#if HAVE_INCOMPATIBLE_CTIME_R +#define asctime_r _incompatible_asctime_r +#define ctime_r _incompatible_ctime_r +#endif /* HAVE_INCOMPATIBLE_CTIME_R */ +#include "sys/types.h" /* for time_t */ +#include "limits.h" /* for CHAR_BIT */ +#endif /* HAVE_GETTEXT */ +#include <sys/wait.h> /* for WIFEXITED and WEXITSTATUS */ +#endif /* HAVE_SYS_WAIT_H */ +#define WIFEXITED(status) (((status) & 0xff) == 0) +#endif /* !defined WIFEXITED */ +#define WEXITSTATUS(status) (((status) >> 8) & 0xff) +#endif /* !defined WEXITSTATUS */ +#include "unistd.h" /* for F_OK and R_OK */ +#endif /* HAVE_UNISTD_H */ +#endif /* !defined F_OK */ +#endif /* !defined R_OK */ +#endif /* !HAVE_UNISTD_H */ +/* Unlike <ctype.h>'s isdigit, this also works if c < 0 | c > UCHAR_MAX. */ +#define is_digit(c) ((unsigned)(c) - '0' <= 9) +** Workarounds for compilers/systems. +** SunOS 4.1.1 cc lacks prototypes. +** SunOS 4.1.1 headers lack EXIT_SUCCESS. +#endif /* !defined EXIT_SUCCESS */ +** SunOS 4.1.1 headers lack EXIT_FAILURE. +#endif /* !defined EXIT_FAILURE */ +** SunOS 4.1.1 headers lack FILENAME_MAX. +#endif /* defined unix */ +#endif /* !defined MAXPATHLEN */ +#define FILENAME_MAX MAXPATHLEN +#endif /* defined MAXPATHLEN */ +#define FILENAME_MAX 1024 /* Pure guesswork */ +#endif /* !defined MAXPATHLEN */ +#endif /* !defined FILENAME_MAX */ +** SunOS 4.1.1 libraries lack remove. +extern int unlink P((const char * filename)); +#endif /* !defined remove */ +** Some ancient errno.h implementations don't declare errno. +** But some newer errno.h implementations define it as a macro. +** Fix the former without affecting the latter. +#endif /* !defined errno */ +** Some time.h implementations don't declare asctime_r. +** Others might define it as a macro. +** Fix the former without affecting the latter. +extern char * asctime_r(); +** Private function declarations. +char * icalloc P((int nelem, int elsize)); +char * icatalloc P((char * old, const char * new)); +char * icpyalloc P((const char * string)); +char * imalloc P((int n)); +void * irealloc P((void * pointer, int size)); +void icfree P((char * pointer)); +void ifree P((char * pointer)); +const char *scheck P((const char *string, const char *format)); +** Finally, some convenience items. +#endif /* !defined TRUE */ +#endif /* !defined FALSE */ +#define TYPE_BIT(type) (sizeof (type) * CHAR_BIT) +#endif /* !defined TYPE_BIT */ +#define TYPE_SIGNED(type) (((type) -1) < 0) +#endif /* !defined TYPE_SIGNED */ +** Since the definition of TYPE_INTEGRAL contains floating point numbers, +** it cannot be used in preprocessor directives. +#define TYPE_INTEGRAL(type) (((type) 0.5) != 0.5) +#endif /* !defined TYPE_INTEGRAL */ +#ifndef INT_STRLEN_MAXIMUM +** 302 / 1000 is log10(2.0) rounded up. +** Subtract one for the sign bit if the type is signed; +** add one for integer division truncation; +** add one more for a minus sign if the type is signed. +#define INT_STRLEN_MAXIMUM(type) \ + ((TYPE_BIT(type) - TYPE_SIGNED(type)) * 302 / 1000 + \ +#endif /* !defined INT_STRLEN_MAXIMUM */ +#endif /* defined lint */ +#endif /* defined __GNUC__ */ +#endif /* !defined lint */ +#endif /* !defined GNUC_or_lint */ +#define INITIALIZE(x) ((x) = 0) +#endif /* defined GNUC_or_lint */ +#endif /* !defined GNUC_or_lint */ +#endif /* !defined INITIALIZE */ +** For the benefit of GNU folk... +** `_(MSGID)' uses the current locale's message library string for MSGID. +** The default is to use gettext if available, and use MSGID otherwise. +#define _(msgid) gettext(msgid) +#else /* !HAVE_GETTEXT */ +#endif /* !HAVE_GETTEXT */ +#endif /* !defined TZ_DOMAIN */ +#if HAVE_INCOMPATIBLE_CTIME_R +char *asctime_r P((struct tm const *, char *)); +char *ctime_r P((time_t const *, char *)); +#endif /* HAVE_INCOMPATIBLE_CTIME_R */ +** UNIX was a registered trademark of The Open Group in 2003. +#endif /* !defined PRIVATE_H */ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytime/recurse.c Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,141 @@
+/************************************************************************* + * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006 + * Licenced under the GNU General Public Licence version 2. + * Provides a function to recurse a directory and call a callback for each + *************************************************************************/ +/* GLibc specific version. In this version, the entries are sorted */ +/* We assume dirname ends in a /, prefix also unless empty */ +recurse_directory_int(char *dirname, char *prefix, DirRecurseMatch func, void *data) + struct dirent **namelist; + if((ents = scandir(dirname, &namelist, 0, alphasort)) < 0) + for (i = 0; i < ents; i++) + asprintf(&ptr, "%s%s", dirname, ent->d_name); + asprintf(&ptr, "%s%s", prefix, ent->d_name); + else if(S_ISDIR(s.st_mode)) + char *newdirname, *newprefix; + if(ent->d_name[0] != '.') + asprintf(&newdirname, "%s%s/", dirname, ent->d_name); + asprintf(&newprefix, "%s%s/", prefix, ent->d_name); + ret = recurse_directory_int(newdirname, newprefix, func, data); +/* generic version, here they are unsorted */ +/* We assume dirname ends in a /, prefix also unless empty */ +recurse_directory_int(char *dirname, char *prefix, DirRecurseMatch func, void *data) + dir = opendir(dirname); + while ((ent = readdir(dir)) != NULL) + asprintf(&ptr, "%s%s", dirname, ent->d_name); + asprintf(&ptr, "%s%s", prefix, ent->d_name); + else if(S_ISDIR(s.st_mode)) + char *newdirname, *newprefix; + if(ent->d_name[0] != '.') + asprintf(&newdirname, "%s%s/", dirname, ent->d_name); + asprintf(&newprefix, "%s%s/", prefix, ent->d_name); + ret = recurse_directory_int(newdirname, newprefix, func, data); +recurse_directory(char *dirname, DirRecurseMatch func, void *data) + char *newdirname = NULL; + if(dirname[strlen(dirname) - 1] != '/') + asprintf(&newdirname, "%s/", dirname); + ret = recurse_directory_int(newdirname ? newdirname : dirname, "", func, data); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytime/recurse.h Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,9 @@
+/************************************************************************* + * Header file for recursion module + * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006 + * Licenced under the GNU General Public Licence version 2. + *************************************************************************/ +typedef int (*DirRecurseMatch)(char *filename, void *data); +int recurse_directory( char *dirname, DirRecurseMatch func, void *data ); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytime/recursetest.c Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,25 @@
+/************************************************************************* + * Recursion test module + * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006 + * Licenced under the GNU General Public Licence version 2. + * Code to test the recursion module. + *************************************************************************/ +process_entry(char *str, void *ptr) + recurse_directory("/usr/share/zoneinfo", process_entry, main); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytime/timetest.c Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,28 @@
+/************************************************************************* + * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006 + * Licenced under the GNU General Public Licence version 2. + * Code to test the timezone module. + *************************************************************************/ + time_t now = time(NULL); + state = timezone_load("Australia/Sydney"); + localsub(&now, 0, &tm, state); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytime/tzfile.h Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,175 @@
+/************************************************************************* + * Private Header file for timezone module + * copied from Olson timezone code, licence unchanged + * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006 + *************************************************************************/ +** This file is in the public domain, so clarified as of +** 1996-06-05 by Arthur David Olson. +** This header is for use ONLY with the time conversion code. +** There is no guarantee that it will remain unchanged, +** or that it will remain at all. +** Do NOT copy it to any system include directory. +** Information about time zone files. +#define TZDIR "/usr/share/zoneinfo" /* Default time zone object file directory */ +#endif /* !defined TZDIR */ +#define TZDEFAULT "localtime" +#endif /* !defined TZDEFAULT */ +#define TZDEFRULES "posixrules" +#endif /* !defined TZDEFRULES */ +** Each file begins with. . . + char tzh_magic[4]; /* TZ_MAGIC */ + char tzh_reserved[16]; /* reserved for future use */ + char tzh_ttisgmtcnt[4]; /* coded number of trans. time flags */ + char tzh_ttisstdcnt[4]; /* coded number of trans. time flags */ + char tzh_leapcnt[4]; /* coded number of leap seconds */ + char tzh_timecnt[4]; /* coded number of transition times */ + char tzh_typecnt[4]; /* coded number of local time types */ + char tzh_charcnt[4]; /* coded number of abbr. chars */ +** . . .followed by. . . +** tzh_timecnt (char [4])s coded transition times a la time(2) +** tzh_timecnt (unsigned char)s types of local time starting at above +** tzh_typecnt repetitions of +** one (char [4]) coded UTC offset in seconds +** one (unsigned char) used to set tm_isdst +** one (unsigned char) that's an abbreviation list index +** tzh_charcnt (char)s '\0'-terminated zone abbreviations +** tzh_leapcnt repetitions of +** one (char [4]) coded leap second transition times +** one (char [4]) total correction after above +** tzh_ttisstdcnt (char)s indexed by type; if TRUE, transition +** time is standard time, if FALSE, +** transition time is wall clock time +** if absent, transition times are +** assumed to be wall clock time +** tzh_ttisgmtcnt (char)s indexed by type; if TRUE, transition +** time is UTC, if FALSE, +** transition time is local time +** if absent, transition times are +** assumed to be local time +** In the current implementation, "tzset()" refuses to deal with files that +** exceed any of the limits below. +** The TZ_MAX_TIMES value below is enough to handle a bit more than a +** year's worth of solar time (corrected daily to the nearest second) or +** 138 years of Pacific Presidential Election time +** (where there are three time zone transitions every fourth year). +#define TZ_MAX_TIMES 370 +#endif /* !defined TZ_MAX_TIMES */ +#define TZ_MAX_TYPES 256 /* Limited by what (unsigned char)'s can hold */ +#endif /* !defined NOSOLAR */ +** Must be at least 14 for Europe/Riga as of Jan 12 1995, +** as noted by Earl Chew. +#define TZ_MAX_TYPES 20 /* Maximum number of local time types */ +#endif /* !defined NOSOLAR */ +#endif /* !defined TZ_MAX_TYPES */ +#define TZ_MAX_CHARS 50 /* Maximum number of abbreviation characters */ + /* (limited by what unsigned chars can hold) */ +#endif /* !defined TZ_MAX_CHARS */ +#define TZ_MAX_LEAPS 50 /* Maximum number of leap second corrections */ +#endif /* !defined TZ_MAX_LEAPS */ +#define DAYSPERNYEAR 365 +#define DAYSPERLYEAR 366 +#define SECSPERHOUR (SECSPERMIN * MINSPERHOUR) +#define SECSPERDAY ((long) SECSPERHOUR * HOURSPERDAY) +#define TM_YEAR_BASE 1900 +#define EPOCH_WDAY TM_THURSDAY +#define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0)) +** Since everything in isleap is modulo 400 (or a factor of 400), we know that +** isleap(y) == isleap(y % 400) +** isleap(a + b) == isleap((a + b) % 400) +** isleap(a + b) == isleap(a % 400 + b % 400) +** This is true even if % means modulo rather than Fortran remainder +** (which is allowed by C89 but not C99). +** We use this to avoid addition overflow problems. +#define isleap_sum(a, b) isleap((a) % 400 + (b) % 400) +#endif /* !defined TZFILE_H */ --- a/chronic/chronic.c Tue Oct 09 06:21:19 2007 -0400
+++ b/chronic/chronic.c Mon Mar 24 05:08:00 2008 -0400
@@ -1,6 +1,6 @@
* Chronic - Remote sound play triggering
- * Copyright (C) 2006 John Bailey <rekkanoryo@rekkanoryo.org>
+ * Copyright (C) 2006-2008 John Bailey <rekkanoryo@rekkanoryo.org> * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -17,6 +17,7 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
+/* If you can't figure out what this line is for, DON'T TOUCH IT. */ #include "../common/pp_internal.h"
--- a/common/Makefile.am Tue Oct 09 06:21:19 2007 -0400
+++ b/common/Makefile.am Mon Mar 24 05:08:00 2008 -0400
@@ -1,5 +1,5 @@
--- a/common/core_template.c Tue Oct 09 06:21:19 2007 -0400
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,84 +0,0 @@
- * Plugin Name - Summary
- * 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., 51 Franklin Street, Fifth Floor, Boston, MA
-#include "../common/pp_internal.h"
-#define PLUGIN_ID "unnamed plugin"
-#define PLUGIN_STATIC_NAME "unnamed"
-#define PLUGIN_AUTHOR "someone <someone@somewhere.tld>"
-plugin_load(PurplePlugin *plugin) {
-plugin_unload(PurplePlugin *plugin) {
-static PurplePluginInfo info = {
- PURPLE_PLUGIN_MAGIC, /* Magic */
- PURPLE_MAJOR_VERSION, /* Purple Major Version */
- PURPLE_MINOR_VERSION, /* Purple Minor Version */
- PURPLE_PLUGIN_STANDARD, /* plugin type */
- NULL, /* ui requirement */
- NULL, /* dependencies */
- PURPLE_PRIORITY_DEFAULT, /* priority */
- PLUGIN_ID, /* plugin id */
- PP_VERSION, /* version */
- NULL, /* description */
- PLUGIN_AUTHOR, /* author */
- PP_WEBSITE, /* website */
- plugin_load, /* load */
- plugin_unload, /* unload */
-init_plugin(PurplePlugin *plugin) {
- bindtextdomain(GETTEXT_PACKAGE, PP_LOCALEDIR);
- bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
- info.name = _("unnamed");
- info.summary = _("summary");
- info.description = _("description");
-PURPLE_INIT_PLUGIN(PLUGIN_STATIC_NAME, init_plugin, info)
--- a/common/glib_compat.h Tue Oct 09 06:21:19 2007 -0400
+++ b/common/glib_compat.h Mon Mar 24 05:08:00 2008 -0400
@@ -24,8 +24,7 @@
#if !GLIB_CHECK_VERSION(2,2,0)
-g_str_has_suffix (const gchar *str,
+g_str_has_suffix (const gchar *str, const gchar *suffix) @@ -43,8 +42,7 @@
-g_str_has_prefix (const gchar *str,
+g_str_has_prefix (const gchar *str, const gchar *prefix) @@ -77,9 +75,7 @@
# define N_(String) (String)
-g_strsplit_set (const gchar *string,
- const gchar *delimiters,
+g_strsplit_set (const gchar *string, const gchar *delimiters, gint max_tokens) gboolean delim_table[256];
--- a/common/gtk_template.c Tue Oct 09 06:21:19 2007 -0400
+++ b/common/gtk_template.c Mon Mar 24 05:08:00 2008 -0400
@@ -1,6 +1,6 @@
+ * Copyright (C) 2004-2008 * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
--- a/common/pp_internal.h Tue Oct 09 06:21:19 2007 -0400
+++ b/common/pp_internal.h Mon Mar 24 05:08:00 2008 -0400
@@ -1,6 +1,6 @@
- * Copyright (C) 2003-2005
+ * Copyright (C) 2003-2008 * See AUTHORS for a list of all authors
* This program is free software; you can redistribute it and/or
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/common/purple_template.c Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,84 @@
+ * Plugin Name - Summary + * Copyright (C) 2004-2008 + * 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., 51 Franklin Street, Fifth Floor, Boston, MA +#include "../common/pp_internal.h" +#define PLUGIN_ID "unnamed plugin" +#define PLUGIN_STATIC_NAME "unnamed" +#define PLUGIN_AUTHOR "someone <someone@somewhere.tld>" +plugin_load(PurplePlugin *plugin) { +plugin_unload(PurplePlugin *plugin) { +static PurplePluginInfo info = { + PURPLE_PLUGIN_MAGIC, /* Magic */ + PURPLE_MAJOR_VERSION, /* Purple Major Version */ + PURPLE_MINOR_VERSION, /* Purple Minor Version */ + PURPLE_PLUGIN_STANDARD, /* plugin type */ + NULL, /* ui requirement */ + NULL, /* dependencies */ + PURPLE_PRIORITY_DEFAULT, /* priority */ + PLUGIN_ID, /* plugin id */ + PP_VERSION, /* version */ + NULL, /* description */ + PLUGIN_AUTHOR, /* author */ + PP_WEBSITE, /* website */ + plugin_load, /* load */ + plugin_unload, /* unload */ +init_plugin(PurplePlugin *plugin) { + bindtextdomain(GETTEXT_PACKAGE, PP_LOCALEDIR); + bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); + info.name = _("unnamed"); + info.summary = _("summary"); + info.description = _("description"); +PURPLE_INIT_PLUGIN(PLUGIN_STATIC_NAME, init_plugin, info) --- a/configure.ac Tue Oct 09 06:21:19 2007 -0400
+++ b/configure.ac Mon Mar 24 05:08:00 2008 -0400
@@ -1,4 +1,4 @@
-AC_INIT([purple-plugin_pack], [2.2.0mtn], [plugins-devel@lists.guifications.org])
+AC_INIT([purple-plugin_pack], [2.4.0mtn], [plugins-devel@lists.guifications.org]) AM_CONFIG_HEADER(pre_config.h)
@@ -230,10 +230,20 @@
-PKG_CHECK_MODULES(GTKSPELL, gtkspell-2.0 >= 2.0.2, , [gtkspell=no])
+PKG_CHECK_MODULES(GTKSPELL, gtkspell-2.0 >= 2.0.2, [], [gtkspell=no]) AC_SUBST(GTKSPELL_CFLAGS)
-AM_CONDITIONAL(HAVE_GTKSPELL, test x"$gtkspell" = x"yes")
+if test x"$gtkspell" = x"yes" ; then + AC_CHECK_HEADER([aspell.h], HAVE_ASPELL_H=yes, HAVE_ASPELL_H=no) + if test x"$HAVE_ASPELL_H" = x"yes" ; then + AC_CHECK_LIB([aspell], [new_aspell_config],BUILD_SWITCH_SPELL=yes,BUILD_SWITCH_SPELL=no) +AM_CONDITIONAL(BUILD_SWITCH_SPELL, test x"$BUILD_SWITCH_SPELL" = x"yes") @@ -300,18 +310,20 @@
+ dewysiwygification/Makefile @@ -342,6 +354,7 @@
xmmsremote/pixmaps/Makefile
@@ -387,7 +400,7 @@
echo Installing finch plugins to......: `eval eval echo $FINCH_LIBDIR`
echo Installing finch plugin data to..: `eval eval echo $FINCH_DATADIR`
if test x"$PP_FINCH_BUILD" = x"" ; then
- echo Finch plugins to be built........: none
+ echo Finch plugins to be built........: none - THIS IS NORMAL echo Finch plugins to be built........:
echo $PP_FINCH_BUILD | xargs -n 4 echo " "
--- a/convbadger/convbadger.c Tue Oct 09 06:21:19 2007 -0400
+++ b/convbadger/convbadger.c Mon Mar 24 05:08:00 2008 -0400
@@ -1,6 +1,6 @@
* ConvBadger - Adds the protocol icon to the menu tray of a conversation
- * Copyright (C) 2007 Gary Kramlich <grim@reaperworld.com>
+ * Copyright (C) 2007-2008 Gary Kramlich <grim@reaperworld.com> * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -16,6 +16,8 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
+/* If you can't figure out what this line is for, DON'T TOUCH IT. */ #include "../common/pp_internal.h"
@@ -24,6 +26,7 @@
/******************************************************************************
@@ -48,11 +51,9 @@
- if(GTK_IS_IMAGE(cbd->icon))
+ if(cbd->icon && GTK_IS_IMAGE(cbd->icon)) gtk_widget_destroy(cbd->icon);
@@ -85,6 +86,9 @@
pidgin_menu_tray_append(PIDGIN_MENU_TRAY(win->menu.tray), cbd->icon,
gtk_widget_show(cbd->icon);
+ g_signal_connect_swapped(G_OBJECT(cbd->icon), "destroy", + G_CALLBACK(g_nullify_pointer), &cbd->icon); @@ -158,7 +162,7 @@
@@ -178,6 +182,11 @@
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/dewysiwygification/Makefile.am Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,26 @@
+EXTRA_DIST = .purple-plugin .build Makefile.mingw +dewysiwygificationdir = $(PURPLE_LIBDIR) +dewysiwygification_la_LDFLAGS = -module -avoid-version +dewysiwygification_LTLIBRARIES = dewysiwygification.la +dewysiwygification_la_SOURCES = \ +dewysiwygification_la_LIBADD = \ + -DLIBDIR=\"$(PURPLE_LIBDIR)\" \ + -DDATADIR=\"$(PURPLE_DATADIR)\" \ + -DPIXMAPSDIR=\"$(PURPLE_PIXMAPSDIR)\" \ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/dewysiwygification/Makefile.mingw Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,12 @@
+# Description: Makefile for dewysiwygification plugin. +include $(PP_TOP)/win_pp.mak --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/dewysiwygification/dewysiwygification.c Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,131 @@
+ * DeWYSIWYGification - Lets you type in HTML without it being escaped to entities. + * Copyright (C) 2004-2008 Tim Ringenbach <omarvo@hotmail.com> + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA. +/* If you can't figure out what this line is for, DON'T TOUCH IT. */ +#include "../common/pp_internal.h" +#define DEWYSIWYGIFICATION_PLUGIN_ID "dewysiwygification" +substitute_words_send_im(PurpleAccount *account, const char *receiver, + if (message == NULL || *message == NULL) + tmp = purple_unescape_html(*message); + purple_debug_misc("dewysiwygification", "it's now: %s", tmp); +substitute_words_send_chat(PurpleAccount *account, char **message, int id) + if (message == NULL || *message == NULL) + tmp = purple_unescape_html(*message); + purple_debug_misc("dewysiwygification", "it's now: %s", tmp); +plugin_load(PurplePlugin *plugin) + void *conv_handle = purple_conversations_get_handle(); + purple_signal_connect(conv_handle, "sending-im-msg", + plugin, PURPLE_CALLBACK(substitute_words_send_im), NULL); + purple_signal_connect(conv_handle, "sending-chat-msg", + plugin, PURPLE_CALLBACK(substitute_words_send_chat), NULL); +static PurplePluginInfo info = + PURPLE_PLUGIN_STANDARD, + PURPLE_PRIORITY_DEFAULT, + DEWYSIWYGIFICATION_PLUGIN_ID, + N_("DeWYSIWYGification Plugin"), + N_("Lets you type in HTML without it being escaped to entities."), + N_("Lets you type in HTML without it being escaped to entities. This will not work well for some protocols. Use \"<\" for a literal \"<\"."), + "Tim Ringenbach <omarvo@hotmail.com>", +init_plugin(PurplePlugin *plugin) + bindtextdomain(GETTEXT_PACKAGE, PP_LOCALEDIR); + bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); + info.name = _(info.name); + info.summary = _(info.summary); + info.description = _(info.description); +PURPLE_INIT_PLUGIN(dewysiwygification, init_plugin, info) --- a/dice/dice.c Tue Oct 09 06:21:19 2007 -0400
+++ b/dice/dice.c Mon Mar 24 05:08:00 2008 -0400
@@ -1,7 +1,8 @@
* Adds a command to roll an arbitrary number of dice with an arbitrary
- * Copyright (C) 2005 Gary Kramlich <grim@reaperworld.com>
+ * Copyright (C) 2005-2008 Gary Kramlich <grim@reaperworld.com> + * Copyright (C) 2007 Lucas <reilithion@gmail.com> * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -18,6 +19,7 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
+/* If you can't figure out what this line is for, DON'T TOUCH IT. */ #include "../common/pp_internal.h"
@@ -28,57 +30,286 @@
+#define BOUNDS_CHECK(var, min, min_def, max, max_def) { \ + else if((var) > (max)) \ +#define ROUND(val) ((gdouble)(val) + 0.5f) static PurpleCmdId dice_cmd_id = 0;
+old_school_roll(gint dice, gint sides) { + GString *str = g_string_new(""); + BOUNDS_CHECK(dice, 1, 2, 15, 15); + BOUNDS_CHECK(sides, 2, 2, 999, 999); + g_string_append_printf(str, "%d %d-sided %s:", + (dice == 1) ? "die" : "dice"); + for(c = 0; c < dice; c++) { + v = rand() % sides + 1; + g_string_append_printf(str, " %d", v); + g_string_free(str, FALSE); +is_dice_notation(const gchar *str) { + return (g_utf8_strchr(str, -1, 'd') != NULL); +dice_notation_roll_helper(const gchar *dn, gint *value) { + GString *str = g_string_new(""); + gchar *ret = NULL, *ms = NULL; + gint dice = 0, sides = 0, i = 0, t = 0, v = 0; + gdouble multiplier = 1.0; + /* at this point, all we have is +/- number for our bonus, so we add it to + if(!is_dice_notation(dn)) { + /* the + makes sure we always have a + or - */ + g_string_append_printf(str, "%s %d", + (bonus < 0) ? "-" : "+", + g_string_free(str, FALSE); + /************************************************************************** + *************************************************************************/ + purple_debug_info("dice", "processing '%s'\n", dn); + /* get the number of dice */ + BOUNDS_CHECK(dice, 1, 1, 999, 999); + /* find and move to the character after the d */ + dn = g_utf8_strchr(dn, -1, 'd'); + /* get the number of sides */ + BOUNDS_CHECK(sides, 2, 2, 999, 999); + /* i've struggled with a better way to determine the next operator, i've + for(t = sides; t > 0; t /= 10) { + purple_debug_info("dice", "looking for the next operator: %s\n", dn); + purple_debug_info("dice", "next operator: %s\n", dn); + /* check if we're multiplying or dividing this block */ + if(*dn == 'x' || *dn == '/') { + multiplier = v = atof(dn); + ms = g_strdup_printf("%d", (gint)multiplier); + /* move past our multiplier */ + for(t = v; t > 0; t /= 10) { + purple_debug_info("dice", "moving past the multiplier: %s\n", dn); + multiplier = 1 / multiplier; + purple_debug_info("dice", "d=%d;s=%d;m=%f;\n", dice, sides, multiplier); + /* calculate and output our block */ + g_string_append_printf(str, " ("); + for(i = 0; i < dice; i++) { + t = rand() % sides + 1; + v = ROUND(t * multiplier); + g_string_append_printf(str, "%s%d", (i > 0) ? " " : "", t); + purple_debug_info("dice", "die %d: %d(%d)\n", i, v, t); + g_string_append_printf(str, ")"); + /* if we have a multiplier, we need to output it as well */ + g_string_append_printf(str, "%c(%s)", op, ms); + /* free our string of the multiplier */ + purple_debug_info("dice", "value=%d;str=%s\n", *value, str->str); + /* we have more in our string, recurse! */ + gchar *s = dice_notation_roll_helper(dn, value); + str = g_string_append(str, s); + g_string_free(str, FALSE); +dice_notation_roll(const gchar *dn) { + GString *str = g_string_new(""); + gchar *ret = NULL, *normalized = NULL; + g_string_append_printf(str, "%s:", dn); + /* normalize the input and process it */ + normalized = g_utf8_strdown(dn, -1); + g_string_append_printf(str, "%s", + dice_notation_roll_helper(normalized, &value)); + g_string_append_printf(str, " = %d", value); + g_string_free(str, FALSE); -roll(PurpleConversation *conv, const gchar *cmd, gchar **args,
- gchar *error, void *data)
+roll(PurpleConversation *conv, const gchar *cmd, gchar **args, gchar *error,
- gint dice = 2, sides = 6, roll, i;
+ gchar *str = NULL, *newcmd = NULL;
+ str = old_school_roll(DEFAULT_DICE, DEFAULT_SIDES); + if(is_dice_notation(args[0])) { + str = dice_notation_roll(args[0]); + sides = (args[1]) ? atoi(args[1]) : DEFAULT_SIDES;
+ str = old_school_roll(dice, sides);
+ i = 1; /* Abuse that iterator! We're saying "We think this is dice notation!" */ + splitted = g_strsplit(args[0], "d", 2); /* Split the description into two parts: (1)d(20+5); discard the 'd'. */ + dice = atoi(splitted[0]); /* We should have the number of dice easily now. */ + if(g_strstr_len(splitted[1], -1, "+") != NULL) /* If our second half contained a '+' (20+5) */ + resplitted = g_strsplit(splitted[1], "+", 2); /* Split again: (20)+(5); discard the '+'. */ + sides = atoi(resplitted[0]); /* Number of sides on the left. */ + bonus += atoi(resplitted[1]); /* Bonus on the right. */ + g_strfreev(resplitted); /* Free memory from the split. */ + else if(g_strstr_len(splitted[1], -1, "-") != NULL) /* If our second half contained a '-' (20-3) */ + resplitted = g_strsplit(splitted[1], "-", 2); /* Split again: (20)-(3); discard the '-'. */ + sides = atoi(resplitted[0]); /* Number of sides on the left. */ + bonus -= atoi(resplitted[1]); /* Penalty on the right. */ + g_strfreev(resplitted); /* Free memory from the split. */ + else /* There was neither a '+' nor a '-' in the second half. */ + sides = atoi(splitted[1]); /* We're assuming it's just a number, then. Number of sides. */ + g_strfreev(splitted); /* Free the original split. */
+ if(args[1] && i == 0) /* If there was a second argument, and we care about it (not dice notation) */ + sides = atoi(args[1]); /* Grab it and make it the number of sides the dice have. */ - g_string_append_printf(str, "Rolls %d %d-sided %s:", dice, sides,
- (dice == 1) ? "die" : "dice");
- for(i = 0; i < dice; i++) {
- roll = rand() % sides + 1;
- g_string_append_printf(str, " %d", roll);
+ if(i) /* Show the output in dice notation format. */ + g_string_append_printf(str, "%dd%d", dice, sides); /* For example, 1d20 */ + g_string_append_printf(str, "+%d", bonus); /* 1d20+5 */ + g_string_append_printf(str, "%d", bonus); /* 1d20-3 (saying "-%d" would be redundant, since the '-' gets output with bonus automatically) */ + g_string_append_printf(str, ":"); /* Final colon. 1d20-4: */ - if(conv->type == PURPLE_CONV_TYPE_IM)
- purple_conv_im_send(PURPLE_CONV_IM(conv), str->str);
- else if(conv->type == PURPLE_CONV_TYPE_CHAT)
- purple_conv_chat_send(PURPLE_CONV_CHAT(conv), str->str);
+ for(i = 0; i < dice; i++) /* For each die... */ + roll = rand() % sides + 1; /* Roll, and add bonus. */ + accumulator += roll; /* Accumulate our rolls */ + g_string_append_printf(str, " %d", roll); /* Append the result of our roll to our output string. */ + if(bonus != 0) /* If we had a bonus */ + accumulator += bonus; /* Accumulate our bonus/penalty */ + g_string_append_printf(str, " %s%d = %d", (bonus < 0) ? "penalty " : "bonus +", bonus, accumulator); /* Append our bonus/penalty to the output string */ + else if(dice > 1) /* Or if we had more than one die */ + g_string_append_printf(str, " = %d", accumulator); /* Append our accumulator */ - g_string_free(str, TRUE);
+ newcmd = g_strdup_printf("me rolls %s", str); + ret = purple_cmd_do_command(conv, newcmd, newcmd, &error); - return PURPLE_CMD_RET_OK;
-plugin_load(PurplePlugin *plugin) {
+plugin_load(PurplePlugin *plugin) - help = _("dice [dice] [sides]: rolls dice number of sides sided dice");
+ help = _("dice [dice] [sides]: rolls dice number of sides sided dice OR\n" + "dice [XdY+-Z]: rolls X number of Y sided dice, giving a Z " + "bonus/penalty to each. e.g. 1d20+2"); dice_cmd_id = purple_cmd_register("dice", "wws", PURPLE_CMD_P_PLUGIN,
PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT |
@@ -86,11 +317,19 @@
NULL, PURPLE_CMD_FUNC(roll),
+ /* we only want to seed this off of the seconds since the epoch once. If + * we do it every time, we'll give the same results for each time we + * process a roll within the same second. This is bad because it's not -plugin_unload(PurplePlugin *plugin) {
+plugin_unload(PurplePlugin *plugin) purple_cmd_unregister(dice_cmd_id);
@@ -98,39 +337,40 @@
static PurplePluginInfo info =
- PURPLE_PLUGIN_MAGIC, /**< magic */
- PURPLE_MAJOR_VERSION, /**< major version */
- PURPLE_MINOR_VERSION, /**< minor version */
- PURPLE_PLUGIN_STANDARD, /**< type */
- NULL, /**< ui_requirement */
- NULL, /**< dependencies */
- PURPLE_PRIORITY_DEFAULT, /**< priority */
+ PURPLE_PLUGIN_STANDARD, + PURPLE_PRIORITY_DEFAULT, - "core-plugin_pack-dice", /**< id */
- PP_VERSION, /**< version */
- NULL, /** description */
- "Gary Kramlich <grim@reaperworld.com>", /**< author */
- PP_WEBSITE, /**< homepage */
+ "core-plugin_pack-dice", + "Gary Kramlich <grim@reaperworld.com>", - plugin_load, /**< load */
- plugin_unload, /**< unload */
- NULL, /**< extra_info */
- NULL, /**< prefs_info */
- NULL, /**< reserved 1 */
- NULL, /**< reserved 2 */
- NULL, /**< reserved 3 */
- NULL /**< reserved 4 */
-init_plugin(PurplePlugin *plugin) {
+init_plugin(PurplePlugin *plugin) bindtextdomain(GETTEXT_PACKAGE, PP_LOCALEDIR);
bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
@@ -139,7 +379,8 @@
info.summary = _("Rolls dice in a chat or im");
info.description = _("Adds a command (/dice) to roll an arbitrary "
- "number of dice with an arbitrary number of sides");
+ "number of dice with an arbitrary number of sides. " + "Now supports dice notation! /help dice for details"); --- a/difftopic/difftopic.c Tue Oct 09 06:21:19 2007 -0400
+++ b/difftopic/difftopic.c Mon Mar 24 05:08:00 2008 -0400
@@ -1,6 +1,6 @@
* DiffTopic - Show the old topic when the topic in a chat room changes.
+ * Copyright (C) 2006-2008 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net> * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
@@ -18,6 +18,7 @@
+/* If you can't figure out what this line is for, DON'T TOUCH IT. */ #include "../common/pp_internal.h"
#define PLUGIN_ID "gtk-plugin_pack-difftopic"
--- a/eight_ball/eight_ball.c Tue Oct 09 06:21:19 2007 -0400
+++ b/eight_ball/eight_ball.c Mon Mar 24 05:08:00 2008 -0400
@@ -1,6 +1,6 @@
- * Copyright (C) 2003-2005
+ * Copyright (C) 2003-2008 * See ../AUTHORS for a list of all authors
* eight_ball: Provides Magic 8-ball-like functionality
@@ -20,6 +20,7 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
+/* If you can't figure out what this line is for, DON'T TOUCH IT. */ #include "../common/pp_internal.h"
@@ -62,12 +63,199 @@
"thats nots really nice",
"Oh I at all do not understand a pancake about what you here talk.",
"it shall be visible will be?",
- "it becomes a complex rainbow of confusion."
+ "it becomes a complex rainbow of confusion.", + "dont sent any message any more stupit n idiot" +#define BOLLOCKS_SIZE 1024 +static const char verbs[][MAXCRAPLEN]={ +static const char adjectives[][MAXCRAPLEN]= { +static const char nouns[][MAXCRAPLEN]={ static PurpleCmdId eight_ball_cmd_id = 0,
+static char *mkbollocks(void) + int verb,adjective,noun; + verb = rand() % G_N_ELEMENTS(verbs); + adjective = (rand()+2) % G_N_ELEMENTS(adjectives); + noun = (rand()+69) % G_N_ELEMENTS(nouns); + return g_strdup_printf("%s %s %s\n", verbs[verb], adjectives[adjective], nouns[noun]); eight_ball_cmd_func(PurpleConversation *conv, const gchar *cmd, gchar **args,
@@ -90,6 +278,10 @@
numstrings = sizeof(fullcrap_strings) / sizeof(fullcrap_strings[0]);
msgprefix = "The Purple Fullcrap Ball says";
+ } else if(!strcmp(cmd, "bollocks")) { + msgprefix = "/dev/bollocks says"; numstrings = sizeof(eight_ball_strings) / sizeof(eight_ball_strings[0]);
msgprefix = "The Purple 8 Ball says";
@@ -107,7 +299,12 @@
- g_string_append_printf(msgstr, "%s: %s", msgprefix, msgs[index]);
+ if (!strcmp(cmd, "bollocks")) { + char *tmp = mkbollocks(); + g_string_append_printf(msgstr, "%s: %s", msgprefix, tmp); + g_string_append_printf(msgstr, "%s: %s", msgprefix, msgs[index]); switch(purple_conversation_get_type(conv)) {
case PURPLE_CONV_TYPE_IM:
@@ -129,11 +326,12 @@
plugin_load(PurplePlugin *plugin)
- const gchar *eight_ball_help, *sg_ball_help, *fullcrap_help;
+ const gchar *eight_ball_help, *sg_ball_help, *fullcrap_help, *bollocks_help; eight_ball_help = _("8ball: sends a random 8ball message");
sg_ball_help = _("sgball: sends a random Stargate Ball message");
fullcrap_help = _("fullcrap: sends random fooling blabber");
+ bollocks_help = _("bollocks: sends random middle-manager bollocks"); eight_ball_cmd_id = purple_cmd_register("8ball", "w", PURPLE_CMD_P_PLUGIN,
PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT |
@@ -153,6 +351,12 @@
PURPLE_CMD_FUNC(eight_ball_cmd_func),
+ bollocks_cmd_id = purple_cmd_register("bollocks", "w", PURPLE_CMD_P_PLUGIN, + PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT | + PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, NULL, + PURPLE_CMD_FUNC(eight_ball_cmd_func), @@ -161,6 +365,8 @@
purple_cmd_unregister(eight_ball_cmd_id);
purple_cmd_unregister(sg_ball_cmd_id);
+ purple_cmd_unregister(fullcrap_cmd_id); + purple_cmd_unregister(bollocks_cmd_id);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/enhancedhist/Makefile.am Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,29 @@
+enhancedhistdir = $(PIDGIN_LIBDIR) +enhancedhist_la_LDFLAGS = -module -avoid-version +enhancedhist_LTLIBRARIES = enhancedhist.la +enhancedhist_la_LIBADD = \ +enhancedhist_la_SOURCES = enhancedhist.c + -DLIBDIR=\"$(PIDGIN_LIBDIR)\" \ + -DDATADIR=\"$(PIDGIN_DATADIR)\" \ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/enhancedhist/Makefile.mingw Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,12 @@
+# Description: Makefile for enhancedhist plugin. +include $(PP_TOP)/win_pp.mak --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/enhancedhist/enhancedhist.c Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,394 @@
+ * enhanced_hist.c - Enhanced History Plugin for libpurple + * Copyright (C) 2004-2008 Andrew Pangborn <gaim@andrewpangborn.com> + * Copyright (C) 2007-2008 John Bailey <rekkanoryo@rekkanoryo.org> + * Copyright (C) 2007 Ankit Singla + * 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. +/* SUMMARY: Puts log of past conversations in the user's IM window and + * allows the user to select the number of conversations to display from + * purple plugin preferences. + * Andrew Pangborn - gaim@andrewpangborn.com +/* If you can't figure out what this line is for, DON'T TOUCH IT. */ +#include "../common/pp_internal.h" +#include <conversation.h> +#define ENHANCED_HISTORY_ID "gtk-plugin_pack-enhanced_history" +#define PREF_ROOT_GPPATH "/plugins/gtk" +#define PREF_ROOT_PPATH "/plugins/gtk/plugin_pack" +#define PREF_ROOT_PATH "/plugins/gtk/plugin_pack/enhanced_history" +#define PREF_NUMBER_PATH "/plugins/gtk/plugin_pack/enhanced_history/number" +#define PREF_MINS_PATH "/plugins/gtk/plugin_pack/enhanced_history/minutes" +#define PREF_HOURS_PATH "/plugins/gtk/plugin_pack/enhanced_history/hours" +#define PREF_DAYS_PATH "/plugins/gtk/plugin_pack/enhanced_history/days" +#define PREF_DATES_PATH "/plugins/gtk/plugin_pack/enhanced_history/dates" +#define PREF_IM_PATH "/plugins/gtk/plugin_pack/enhanced_history/im" +#define PREF_CHAT_PATH "/plugins/gtk/plugin_pack/enhanced_history/chat" +#define PREF_NUMBER_VAL purple_prefs_get_int(PREF_NUMBER_PATH) +#define PREF_MINS_VAL purple_prefs_get_int(PREF_MINS_PATH) +#define PREF_HOURS_VAL purple_prefs_get_int(PREF_HOURS_PATH) +#define PREF_DAYS_VAL purple_prefs_get_int(PREF_DAYS_PATH) +#define PREF_DATES_VAL purple_prefs_get_bool(PREF_DATES_PATH) +#define PREF_IM_VAL purple_prefs_get_bool(PREF_IM_PATH) +#define PREF_CHAT_VAL purple_prefs_get_bool(PREF_CHAT_PATH) +static gboolean _scroll_imhtml_to_end(gpointer data) + GtkIMHtml *imhtml = data; + gtk_imhtml_scroll_to_end(GTK_IMHTML(imhtml), FALSE); + g_object_unref(G_OBJECT(imhtml)); +static void historize(PurpleConversation *c) + PurpleAccount *account = NULL; + PurpleConversationType convtype; + PidginConversation *gtkconv = NULL; + GtkIMHtmlOptions options; + GSList *buddies = NULL; + const char *name = NULL, *alias = NULL, *LOG_MODE = NULL; + char *header = NULL, *protocol = NULL, *history = NULL; + account = purple_conversation_get_account(c); + name = purple_conversation_get_name(c); + LOG_MODE = purple_prefs_get_string("/purple/logging/format"); + /* If logging isn't enabled, don't show any history */ + if(!purple_prefs_get_bool("/purple/logging/log_ims") && + !purple_prefs_get_bool("/purple/logging/log_chats")) + /* If the user wants to show 0 logs, stop now */ + if(PREF_NUMBER_VAL == 0) { + /* If the logging mode is html, set the output options to include no newline. + * Otherwise, it's normal text, so we don't need extra lines */ + if(strcasecmp(LOG_MODE, "html")==0) { + options = GTK_IMHTML_NO_NEWLINE; + options = GTK_IMHTML_NO_COLOURS; + /* Find buddies for this conversation. */ + buddies = purple_find_buddies(account, name); + /* If we found at least one buddy, save the first buddy's alias. */ + alias = purple_buddy_get_contact_alias((PurpleBuddy *)buddies->data); + /* Determine whether this is an IM or a chat. In either case, if the user has that + * particular log type disabled, the logs file doesnt not get specified */ + convtype = purple_conversation_get_type(c); + if (convtype == PURPLE_CONV_TYPE_IM && PREF_IM_VAL) { + logs = purple_log_get_logs(PURPLE_LOG_IM, + purple_conversation_get_name(c), purple_conversation_get_account(c)); + } else if (convtype == PURPLE_CONV_TYPE_CHAT && PREF_CHAT_VAL) { + logs = purple_log_get_logs(PURPLE_LOG_CHAT, + purple_conversation_get_name(c), purple_conversation_get_account(c)); + /* The logs are non-existant or the user has disabled this type for log displaying. */ + gtkconv = PIDGIN_CONVERSATION(c); + size = g_list_length(logs); + /* Make sure the user selected number of chats does not exceed the number of logs. */ + if(size > PREF_NUMBER_VAL) { + /* No idea wth this does, it was in the original history plugin */ + if (flags & PURPLE_LOG_READ_NO_NEWLINE) { + options |= GTK_IMHTML_NO_NEWLINE; + /* Deal with time limitations */ + if(PREF_MINS_VAL == 0 && PREF_HOURS_VAL == 0 && PREF_DAYS_VAL == 0) { + /* No time limitations, advance the logs PREF_NUMBER_VAL-1 forward */ + while(logs->next && counter < (PREF_NUMBER_VAL - 1)) { + purple_debug_info("ehnahcedhist", "Counter: %d\n", counter); + struct tm *log_tm = NULL, *local_tm = NULL; + double limit_time, diff_time; + /* Grab current time and normalize it to UTC */ + /* Pull the local time from the purple log, convert it to UTC time */ + log_tm = gmtime(&((PurpleLog*)logs->data)->time); + log_time = mktime(log_tm); + purple_debug_info("enhancedhist", "Local Time as int: %d \n", (int)t); + purple_debug_info("enhancedhist", "Log Time as int: %d \n", (int)mktime(log_tm)); + limit_time = (PREF_MINS_VAL * 60.0) + (PREF_HOURS_VAL * 60.0 * 60.0) + + (PREF_DAYS_VAL * 60.0 * 60.0 * 24.0); + diff_time = difftime(t, log_time); + purple_debug_info("enhancedhist", "Time difference between local and log: %.21f \n", + /* The most recent log is already too old, so lets return */ + if(diff_time > limit_time) { + /* Iterate to the end of the list, stop while messages are under limit, we just + while(logs->next && diff_time <= limit_time && counter < (PREF_NUMBER_VAL - 1)) { + log_tm = gmtime(&((PurpleLog*)logs->data)->time); + log_time = mktime(log_tm); + diff_time = difftime(t, log_time); + if(diff_time > limit_time) { + /* Loop through the logs and print them to the window */ + protocol = g_strdup(gtk_imhtml_get_protocol_name(GTK_IMHTML(gtkconv->imhtml))); + gtk_imhtml_set_protocol_name(GTK_IMHTML(gtkconv->imhtml), + purple_account_get_protocol_name(((PurpleLog*)logs->data)->account)); + if (gtk_text_buffer_get_char_count(gtk_text_view_get_buffer( + GTK_TEXT_VIEW(gtkconv->imhtml)))) + gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), "<BR>", options); + /* Print a header at the beginning of the log */ + header = g_strdup_printf(_("<b>Conversation with %s on %s:</b><br>"), alias, + purple_date_format_full(localtime(&((PurpleLog *)logs->data)->time))); + gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), header, options); + /* Copy the log string into the history array */ + history = purple_log_read((PurpleLog*)logs->data, &flags); + gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), history, options); + /* Advance the list so that the next time through the loop we get the next log */ + gtk_imhtml_set_protocol_name(GTK_IMHTML(gtkconv->imhtml), protocol); + g_object_ref(G_OBJECT(gtkconv->imhtml)); + g_idle_add(_scroll_imhtml_to_end, gtkconv->imhtml); + /* Clear the allocated memory that the logs are using */ + g_list_foreach(logs, (GFunc)purple_log_free, NULL); +plugin_load(PurplePlugin *plugin) + purple_signal_connect(purple_conversations_get_handle(), + "conversation-created", plugin, PURPLE_CALLBACK(historize), NULL); +eh_prefs_get_frame(PurplePlugin *plugin) + GtkSizeGroup *sg = NULL; + GtkWidget *vbox = NULL, *frame = NULL, *option = NULL; + sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); + vbox = gtk_vbox_new(TRUE, PIDGIN_HIG_BOX_SPACE); + /* heading for the more general options */ + frame = pidgin_make_frame(vbox, _("Display Options")); + /* the integer pref for the number of logs to display */ + pidgin_prefs_labeled_spin_button(frame, _("Number of previous conversations to display:"), + PREF_NUMBER_PATH, 0, 255, NULL); + /* the boolean preferences */ + option = pidgin_prefs_checkbox(_("Show dates with text"), PREF_DATES_PATH, frame); + option = pidgin_prefs_checkbox(_("Show logs for IMs"), PREF_IM_PATH, frame); + option = pidgin_prefs_checkbox(_("Show logs for chats"), PREF_CHAT_PATH, frame); + /* heading for the age limit options */ + frame = pidgin_make_frame(vbox, _("Age Limit for Logs (0 to disable):")); + /* the integer preferences for time limiting */ + option = pidgin_prefs_labeled_spin_button(frame, "Days:", PREF_DAYS_PATH, 0, 255, sg); + option = pidgin_prefs_labeled_spin_button(frame, "Hours:", PREF_HOURS_PATH, 0, 255, sg); + option = pidgin_prefs_labeled_spin_button(frame, "Minutes:", PREF_MINS_PATH, 0, 255, sg); + gtk_widget_show_all(vbox); +static PidginPluginUiInfo ui_info = { + eh_prefs_get_frame, /* get prefs frame */ + 0, /* page number - reserved */ + /* reserved pointers */ +static PurplePluginInfo info = + PURPLE_PLUGIN_STANDARD, + PURPLE_PRIORITY_DEFAULT, + "Andrew Pangborn <gaim@andrewpangborn.com>", +init_plugin(PurplePlugin *plugin) + gboolean dates = FALSE, ims = FALSE, chats = FALSE; + purple_prefs_add_none(PREF_ROOT_GPPATH); + purple_prefs_add_none(PREF_ROOT_PPATH); + purple_prefs_add_none(PREF_ROOT_PATH); + if(purple_prefs_exists("/plugins/core/enhanced_history/int")) { + if(strcmp(purple_prefs_get_string("/plugins/core/enhanced_history/string_date"), "no")) + if(strcmp(purple_prefs_get_string("/plugins/core/enhanced_history/string_im"), "no")) + if(strcmp(purple_prefs_get_string("/plugins/core/enhanced_history/string_chat"), "no")) + purple_prefs_add_int(PREF_NUMBER_PATH, purple_prefs_get_int("/plugins/core/enhanced_history/int")); + purple_prefs_add_int(PREF_MINS_PATH, purple_prefs_get_int("/plugins/core/enhanced_history/mins")); + purple_prefs_add_int(PREF_HOURS_PATH, purple_prefs_get_int("/plugins/core/enhanced_history/hours")); + purple_prefs_add_int(PREF_DAYS_PATH, purple_prefs_get_int("/plugins/core/enhanced_history/days")); + purple_prefs_add_bool(PREF_DATES_PATH, dates); + purple_prefs_add_bool(PREF_IM_PATH, ims); + purple_prefs_add_bool(PREF_CHAT_PATH, chats); + purple_prefs_remove("/plugins/core/enhanced_history/int"); + purple_prefs_remove("/plugins/core/enhanced_history/mins"); + purple_prefs_remove("/plugins/core/enhanced_history/hours"); + purple_prefs_remove("/plugins/core/enhanced_history/days"); + purple_prefs_remove("/plugins/core/enhanced_history/string_date"); + purple_prefs_remove("/plugins/core/enhanced_history/string_im"); + purple_prefs_remove("/plugins/core/enhanced_history/string_chat"); + purple_prefs_remove("/plugins/core/enhanced_history"); + /* Create these prefs with sensible defaults */ + purple_prefs_add_int(PREF_NUMBER_PATH, 1); + purple_prefs_add_int(PREF_MINS_PATH, 0); + purple_prefs_add_int(PREF_HOURS_PATH, 0); + purple_prefs_add_int(PREF_DAYS_PATH, 0); + purple_prefs_add_bool(PREF_DATES_PATH, TRUE); + purple_prefs_add_bool(PREF_IM_PATH, TRUE); + purple_prefs_add_bool(PREF_CHAT_PATH, FALSE); + info.name = _("Enhanced History"); + info.summary = _("An enhanced version of the history plugin."); + info.description = _("An enhanced versoin of the history plugin. Grants ability to " + "select the number of previous conversations to show instead of just one."); +PURPLE_INIT_PLUGIN(enhanced_history, init_plugin, info) --- a/findip/findip.c Tue Oct 09 06:21:19 2007 -0400
+++ b/findip/findip.c Mon Mar 24 05:08:00 2008 -0400
@@ -1,6 +1,6 @@
* Find IP - Find the IP of a person in the buddylist
+ * Copyright (C) 2007-2008 * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
@@ -17,17 +17,12 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# include "../pp_config.h"
-#define PLUGIN_ID "findip"
-#define PLUGIN_NAME "Find IP"
+/* If you can't figure out what this line is for, DON'T TOUCH IT. */ +#include "../common/pp_internal.h" +#define PLUGIN_ID "core-plugin_pack-findip" #define PLUGIN_STATIC_NAME "findip"
-#define PLUGIN_SUMMARY "Find the IP of a person in the buddylist."
-#define PLUGIN_DESCRIPTION "Find the IP of a person in the buddylist. This doesn't really work."
#define PLUGIN_AUTHOR "someone <someone@somewhere.tld>"
@@ -40,10 +35,10 @@
-/* Pack/Local headers */
+/* If you can't figure out what this line is for, DON'T TOUCH IT. */ #include "../common/pp_internal.h"
-#define PREF_ROOT "/plugins/core/" PLUGIN_ID
+#define PREF_ROOT "/plugins/core/plugin_pack/" PLUGIN_STATIC_NAME #define PREF_NOTIFY PREF_ROOT "/notify"
@@ -138,22 +133,22 @@
static PurplePluginInfo info = {
- PURPLE_PLUGIN_MAGIC, /* Magic */
- PURPLE_MAJOR_VERSION, /* Purple Major Version */
- PURPLE_MINOR_VERSION, /* Purple Minor Version */
+ PURPLE_PLUGIN_MAGIC, /* Magic */ + PURPLE_MAJOR_VERSION, /* Purple Major Version */ + PURPLE_MINOR_VERSION, /* Purple Minor Version */ PURPLE_PLUGIN_STANDARD, /* plugin type */
NULL, /* ui requirement */
- PURPLE_PRIORITY_DEFAULT, /* priority */
+ PURPLE_PRIORITY_DEFAULT, /* priority */ PLUGIN_ID, /* plugin id */
- PP_VERSION, /* version */
+ PP_VERSION, /* version */ PLUGIN_AUTHOR, /* author */
- PP_WEBSITE, /* website */
+ PP_WEBSITE , /* website */ plugin_unload, /* unload */
@@ -172,9 +167,9 @@
bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
- info.name = _(PLUGIN_NAME);
- info.summary = _(PLUGIN_SUMMARY);
- info.description = _(PLUGIN_DESCRIPTION);
+ info.name = _("Find IP"); + info.summary = _("Find the IP of a person in the buddylist."); + info.description = _("Find the IP of a person in the buddylist. This doesn't really work."); purple_prefs_add_none(PREF_ROOT);
purple_prefs_add_bool(PREF_NOTIFY, TRUE);
--- a/gRIM/gRIM.c Tue Oct 09 06:21:19 2007 -0400
+++ b/gRIM/gRIM.c Mon Mar 24 05:08:00 2008 -0400
@@ -2,7 +2,11 @@
* A completely stupid plugin, inspired by a dumb conversation in #gaim
* and needing some light relief from 'real' work.
* Also as a tribute to our fearless project leader.
- * Copyright (C) 2005 Peter Lawler <bleeter from users.sf.net>
+ * Copyright (C) 2005-2008 Peter Lawler <bleeter from users.sf.net> + * Copyright (C) 2006-2008 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net> + * Copyright (C) 2006-2008 John Bailey <rekkanoryo@rekkanoryo.org> + * Copyright (C) 2007 Ankit Singla * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -26,6 +30,7 @@
* 08:19 < grim> i was going to use my nick as an example but decided against that..
+/* If you can't figure out what this line is for, DON'T TOUCH IT. */ #include "../common/pp_internal.h"
@@ -99,7 +104,7 @@
PurpleConversation *conv;
-static PurpleCmdId rim_cmd_id = 0;
+static PurpleCmdId rim_cmd_id = 0, base_cmd_id = 0; timeout_func_cb(struct timeout_data *data)
@@ -280,7 +285,7 @@
/* THIS LINE IS NOT TRANSLATABLE. Patches to make it NLS capable will be
* rejected without response */
help = "gRIM: Take off every 'Zig'!!";
- rim_cmd_id = purple_cmd_register("base", "", PURPLE_CMD_P_PLUGIN,
+ base_cmd_id = purple_cmd_register("base", "", PURPLE_CMD_P_PLUGIN, PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT,
NULL, PURPLE_CMD_FUNC(rim),
@@ -290,6 +295,7 @@
plugin_unload(PurplePlugin *plugin) {
purple_cmd_unregister(rim_cmd_id);
+ purple_cmd_unregister(base_cmd_id); --- a/groupmsg/groupmsg.c Tue Oct 09 06:21:19 2007 -0400
+++ b/groupmsg/groupmsg.c Mon Mar 24 05:08:00 2008 -0400
@@ -1,6 +1,6 @@
* GroupMsg - Send an IM to a group of buddies
- * Copyright (C) 2004 Stu Tomlinson <stu@nosnilmot.com>
+ * Copyright (C) 2004-2008 Stu Tomlinson <stu@nosnilmot.com> * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -17,6 +17,7 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
+/* If you can't figure out what this line is for, DON'T TOUCH IT. */ #include "../common/pp_internal.h"
--- a/hideconv/Makefile.am Tue Oct 09 06:21:19 2007 -0400
+++ b/hideconv/Makefile.am Mon Mar 24 05:08:00 2008 -0400
@@ -1,4 +1,4 @@
-EXTRA_DIST = .pidgin-plugin Makefile.mingw .build
+EXTRA_DIST = .pidgin-plugin Makefile.mingw .incomplete hideconvdir = $(PIDGIN_LIBDIR)
--- a/hideconv/hideconv.c Tue Oct 09 06:21:19 2007 -0400
+++ b/hideconv/hideconv.c Mon Mar 24 05:08:00 2008 -0400
@@ -1,6 +1,6 @@
* Hide Conversations - You can hide conversations without having to close them.
+ * Copyright (C) 2007-2008 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net> * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
@@ -18,6 +18,7 @@
+/* If you can't figure out what this line is for, DON'T TOUCH IT. */ #include "../common/pp_internal.h"
#define PLUGIN_ID "gtk-plugin_pack-hideconv"
@@ -149,7 +150,7 @@
PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv);
+ if (!gtkconv || !gtkconv->win || !gtkconv->win->window) if (!GTK_WIDGET_VISIBLE(gtkconv->win->window))
--- a/highlight/highlight.c Tue Oct 09 06:21:19 2007 -0400
+++ b/highlight/highlight.c Mon Mar 24 05:08:00 2008 -0400
@@ -1,7 +1,7 @@
* highlight.c Highlight on customized words.
- * Copyright (C) 2007 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net>
+ * Copyright (C) 2007-2008 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net> * This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
--- a/ignore/ignore.c Tue Oct 09 06:21:19 2007 -0400
+++ b/ignore/ignore.c Mon Mar 24 05:08:00 2008 -0400
@@ -1,7 +1,7 @@
* @file ignore.c Ignore people.
- * Copyright (C) 2007 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net>
+ * Copyright (C) 2007-2008 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net> * This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -18,7 +18,7 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
-/* Pack/Local headers */
+/* If you can't figure out what this line is for, DON'T TOUCH IT. */ #include "../common/pp_internal.h"
@@ -27,6 +27,7 @@
#include <conversation.h>
@@ -116,7 +117,7 @@
char *split = strrchr(pref, '/');
+ if (rule && *rule != 'n') { if (last == NULL || g_strcasecmp(last, pref)) {
--- a/infopane/infopane.c Tue Oct 09 06:21:19 2007 -0400
+++ b/infopane/infopane.c Mon Mar 24 05:08:00 2008 -0400
@@ -1,6 +1,6 @@
* Infopane - Use different views for the details information in conversation windows.
+ * Copyright (C) 2007-2008 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net> * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
@@ -18,6 +18,7 @@
+/* If you can't figure out what this line is for, DON'T TOUCH IT. */ #include "../common/pp_internal.h"
@@ -123,6 +124,12 @@
+call_ensure_tabs_are_showing(PurpleConversation *conv) + g_timeout_add(0, (GSourceFunc)ensure_tabs_are_showing, conv); pref_changed(gpointer data, ...)
GList *wins = pidgin_conv_windows_get_list();
@@ -159,7 +166,7 @@
purple_signal_connect(pidgin_conversations_get_handle(),
- plugin, PURPLE_CALLBACK(ensure_tabs_are_showing), NULL);
+ plugin, PURPLE_CALLBACK(call_ensure_tabs_are_showing), NULL); purple_prefs_connect_callback(plugin, PREF_POSITION, (PurplePrefCallback)pref_changed, NULL);
purple_prefs_connect_callback(plugin, PREF_DRAG, (PurplePrefCallback)pref_changed, NULL);
@@ -219,7 +226,7 @@
"Sadrul H Chowdhury <sadrul@pidgin.im>",
--- a/irc-more/irc-more.c Tue Oct 09 06:21:19 2007 -0400
+++ b/irc-more/irc-more.c Mon Mar 24 05:08:00 2008 -0400
@@ -1,7 +1,8 @@
* @file irc-more.c A couple of additional IRC features.
- * Copyright (C) 2007 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net>
+ * Copyright (C) 2007-2008 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net> + * Copyright (C) 2007-2008 John Bailey <rekkanoryo@rekkanoryo.org> * This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -18,7 +19,7 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
-/* Pack/Local headers */
+/* If you can't figure out what this line is for, DON'T TOUCH IT. */ #include "../common/pp_internal.h"
@@ -32,7 +33,8 @@
#define CTCP_REPLY purple_account_get_string(account, "ctcp-message", "Purple IRC")
#define PART_MESSAGE purple_account_get_string(account, "part-message", "Leaving.")
#define QUIT_MESSAGE purple_account_get_string(account, "quit-message", "Leaving.")
-#define UMODES purple_account_get_string(account, "umodes", "i")
+#define SET_UMODES purple_account_get_string(account, "umodes", "i") +#define UNSET_UMODES purple_account_get_string(account, "umodes", NULL) #define PLUGIN_ID "core-plugin_pack-irc-more"
@@ -95,8 +97,8 @@
/* should this be done on a timeout? */
PurpleAccount *account = NULL;
- const gchar *nick = NULL, *modes = NULL;
+ const gchar *nick = NULL, *setmodes = NULL, *unsetmodes = NULL; + gchar *msg = NULL, *msg2 = NULL; account = purple_connection_get_account(gc);
@@ -105,16 +107,23 @@
nick = purple_connection_get_display_name(gc);
- msg = g_strdup_printf("MODE %s +%s\r\n", nick, modes);
+ unsetmodes = UNSET_UMODES; + msg = g_strdup_printf("MODE %s +%s\r\n", nick, setmodes); irc_info->send_raw(gc, msg, strlen(msg));
+ if(unsetmodes && *unsetmodes) { + msg2 = g_strdup_printf("MODE %s -%s\r\n", nick, unsetmodes); + irc_info->send_raw(gc, msg2, strlen(msg2)); +#if !PURPLE_VERSION_CHECK(2,4,0) notice_cmd_cb(PurpleConversation *conv, const gchar *cmd, gchar **args,
gchar **error, void *data)
@@ -157,19 +166,20 @@
irc_info->send_raw(gc, msg, len);
/* avoid a possible double-free crash */
return PURPLE_CMD_RET_OK;
irc_sending_text(PurpleConnection *gc, char **msg, gpointer null)
PurpleAccount *account = purple_connection_get_account(gc);
char *message = strchr(*msg, ':');
@@ -190,8 +200,8 @@
*msg = g_strdup_printf("%s:\001VERSION %s\001\r\n", *msg, CTCP_REPLY);
@@ -211,9 +221,11 @@
/* specify our help string and register our command */
notice_help = _("notice target message: Send a notice to the specified target.");
+#if !PURPLE_VERSION_CHECK(2,4,0) notice_cmd_id = purple_cmd_register("notice", "ws", PURPLE_CMD_P_PLUGIN,
PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY,
"prpl-irc", notice_cmd_cb, notice_help, NULL);
/* we need this handle for the signed-on signal */
gc_handle = purple_connections_get_handle();
@@ -238,9 +250,11 @@
option = purple_account_option_string_new(_("Default Part Message"), "part-message", "Leaving.");
irc_info->protocol_options = g_list_append(irc_info->protocol_options, option);
- option = purple_account_option_string_new(_("User Modes On Connect"), "umodes", "i");
+ option = purple_account_option_string_new(_("Set User Modes On Connect"), "setumodes", "i"); irc_info->protocol_options = g_list_append(irc_info->protocol_options, option);
+ option = purple_account_option_string_new(_("Unset User Modes On Connect"), "unsetumodes", ""); + irc_info->protocol_options = g_list_append(irc_info->protocol_options, option); --- a/irchelper/irchelper.c Tue Oct 09 06:21:19 2007 -0400
+++ b/irchelper/irchelper.c Mon Mar 24 05:08:00 2008 -0400
@@ -1,7 +1,7 @@
* IRC Helper Plugin for libpurple
- * Copyright (C) 2005-2007, Richard Laager <rlaager@pidgin.im>
+ * Copyright (C) 2005-2008, Richard Laager <rlaager@pidgin.im> * Copyright (C) 2004-2005, Mathias Hasselmann <mathias@taschenorakel.de>
* Copyright (C) 2005, Daniel Beardsmore <uilleann@users.sf.net>
* Copyright (C) 2005, Björn Nilsson <BNI on irc.freenode.net>
@@ -23,6 +23,7 @@
+/* If you can't figure out what this line is for, DON'T TOUCH IT. */ #include "../common/pp_internal.h"
@@ -198,7 +199,12 @@
conv = g_new0(PurpleConversation, 1);
conv->type = PURPLE_CONV_TYPE_IM;
- purple_conversation_set_account(conv, account);
+ /* If we use this then the conversation updated signal is fired and + * other plugins might start doing things to our conversation, such as + * setting data on it which we would then need to free etc. It's easier + * just to be more hacky by setting account directly. */ + /* purple_conversation_set_account(conv, account); */ + conv->account = account; --- a/irssi/datechange.c Tue Oct 09 06:21:19 2007 -0400
+++ b/irssi/datechange.c Mon Mar 24 05:08:00 2008 -0400
@@ -1,7 +1,8 @@
* irssi - Implements several irssi features for Purple
- * Copyright (C) 2005-2007 Gary Kramlich <grim@reaperworld.com>
- * Copyright (C) 2007 John Bailey <rekkanoryo@rekkanoryo.org>
+ * Copyright (C) 2005-2008 Gary Kramlich <grim@reaperworld.com> + * Copyright (C) 2006-2008 John Bailey <rekkanoryo@rekkanoryo.org> + * Copyright (C) 2006-2008 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net> * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
--- a/irssi/datechange.h Tue Oct 09 06:21:19 2007 -0400
+++ b/irssi/datechange.h Mon Mar 24 05:08:00 2008 -0400
@@ -1,7 +1,8 @@
* irssi - Implements several irssi features for Purple
- * Copyright (C) 2005-2007 Gary Kramlich <grim@reaperworld.com>
- * Copyright (C) 2007 John Bailey <rekkanoryo@rekkanoryo.org>
+ * Copyright (C) 2005-2008 Gary Kramlich <grim@reaperworld.com> + * Copyright (C) 2006-2008 John Bailey <rekkanoryo@rekkanoryo.org> + * Copyright (C) 2006-2008 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net> * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
--- a/irssi/irssi.c Tue Oct 09 06:21:19 2007 -0400
+++ b/irssi/irssi.c Mon Mar 24 05:08:00 2008 -0400
@@ -1,7 +1,8 @@
* irssi - Implements several irssi features for Purple
- * Copyright (C) 2005-2006 Gary Kramlich <grim@reaperworld.com>
- * Copyright (C) 2006 John Bailey <rekkanoryo@rekkanoryo.org>
+ * Copyright (C) 2005-2008 Gary Kramlich <grim@reaperworld.com> + * Copyright (C) 2006-2008 John Bailey <rekkanoryo@rekkanoryo.org> + * Copyright (C) 2006-2008 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net> * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
@@ -19,6 +20,7 @@
+/* If you can't figure out what this line is for, DON'T TOUCH IT. */ #include "../common/pp_internal.h"
/* define these so the plugin info struct way at the bottom is cleaner */
--- a/irssi/lastlog.c Tue Oct 09 06:21:19 2007 -0400
+++ b/irssi/lastlog.c Mon Mar 24 05:08:00 2008 -0400
@@ -1,7 +1,8 @@
* irssi - Implements several irssi features for Purple
- * Copyright (C) 2005-2007 Gary Kramlich <grim@reaperworld.com>
- * Copyright (C) 2007 John Bailey <rekkanoryo@rekkanoryo.org>
+ * Copyright (C) 2005-2008 Gary Kramlich <grim@reaperworld.com> + * Copyright (C) 2006-2008 John Bailey <rekkanoryo@rekkanoryo.org> + * Copyright (C) 2006-2008 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net> * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
@@ -19,6 +20,7 @@
+/* If you can't figure out what this line is for, DON'T TOUCH IT. */ #include "../common/pp_internal.h"
--- a/irssi/lastlog.h Tue Oct 09 06:21:19 2007 -0400
+++ b/irssi/lastlog.h Mon Mar 24 05:08:00 2008 -0400
@@ -1,7 +1,8 @@
* irssi - Implements several irssi features for Purple
- * Copyright (C) 2005-2007 Gary Kramlich <grim@reaperworld.com>
- * Copyright (C) 2007 John Bailey <rekkanoryo@rekkanoryo.org>
+ * Copyright (C) 2005-2008 Gary Kramlich <grim@reaperworld.com> + * Copyright (C) 2006-2008 John Bailey <rekkanoryo@rekkanoryo.org> + * Copyright (C) 2006-2008 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net> * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
--- a/irssi/layout.c Tue Oct 09 06:21:19 2007 -0400
+++ b/irssi/layout.c Mon Mar 24 05:08:00 2008 -0400
@@ -1,7 +1,8 @@
* irssi - Implements several irssi features for Purple
- * Copyright (C) 2005-2007 Gary Kramlich <grim@reaperworld.com>
- * Copyright (C) 2007 John Bailey <rekkanoryo@rekkanoryo.org>
+ * Copyright (C) 2005-2008 Gary Kramlich <grim@reaperworld.com> + * Copyright (C) 2006-2008 John Bailey <rekkanoryo@rekkanoryo.org> + * Copyright (C) 2006-2008 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net> * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
@@ -19,6 +20,7 @@
+/* If you can't figure out what this line is for, DON'T TOUCH IT. */ #include "../common/pp_internal.h"
--- a/irssi/layout.h Tue Oct 09 06:21:19 2007 -0400
+++ b/irssi/layout.h Mon Mar 24 05:08:00 2008 -0400
@@ -1,7 +1,8 @@
* irssi - Implements several irssi features for Purple
- * Copyright (C) 2005-2007 Gary Kramlich <grim@reaperworld.com>
- * Copyright (C) 2007 John Bailey <rekkanoryo@rekkanoryo.org>
+ * Copyright (C) 2005-2008 Gary Kramlich <grim@reaperworld.com> + * Copyright (C) 2006-2008 John Bailey <rekkanoryo@rekkanoryo.org> + * Copyright (C) 2006-2008 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net> * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
--- a/irssi/textfmt.c Tue Oct 09 06:21:19 2007 -0400
+++ b/irssi/textfmt.c Mon Mar 24 05:08:00 2008 -0400
@@ -1,7 +1,8 @@
* irssi - Implements several irssi features for Purple
- * Copyright (C) 2005-2007 Gary Kramlich <grim@reaperworld.com>
- * Copyright (C) 2007 John Bailey <rekkanoryo@rekkanoryo.org>
+ * Copyright (C) 2005-2008 Gary Kramlich <grim@reaperworld.com> + * Copyright (C) 2006-2008 John Bailey <rekkanoryo@rekkanoryo.org> + * Copyright (C) 2006-2008 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net> * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
@@ -19,6 +20,7 @@
+/* If you can't figure out what this line is for, DON'T TOUCH IT. */ #include "../common/pp_internal.h"
@@ -102,8 +104,10 @@
/* now let's replce the matches.
* If we don't have any, drop out right away.
- if(regexec(®ex, iter, GROUP_TOTAL, matches, 0) != 0)
+ if(regexec(®ex, iter, GROUP_TOTAL, matches, 0) != 0) { /* create our GString. Heh heh */
@@ -143,6 +147,8 @@
iter += matches[GROUP_ALL].rm_eo;
} while(regexec(®ex, iter, GROUP_TOTAL, matches, REG_NOTBOL) == 0);
/* at this point, iter is either the remains of the text, of a single null
* terminator. So throw it onto the GString.
--- a/irssi/textfmt.h Tue Oct 09 06:21:19 2007 -0400
+++ b/irssi/textfmt.h Mon Mar 24 05:08:00 2008 -0400
@@ -1,7 +1,8 @@
* irssi - Implements several irssi features for Purple
- * Copyright (C) 2005-2007 Gary Kramlich <grim@reaperworld.com>
- * Copyright (C) 2007 John Bailey <rekkanoryo@rekkanoryo.org>
+ * Copyright (C) 2005-2008 Gary Kramlich <grim@reaperworld.com> + * Copyright (C) 2006-2008 John Bailey <rekkanoryo@rekkanoryo.org> + * Copyright (C) 2006-2008 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net> * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
--- a/irssi/window.c Tue Oct 09 06:21:19 2007 -0400
+++ b/irssi/window.c Mon Mar 24 05:08:00 2008 -0400
@@ -1,7 +1,8 @@
* irssi - Implements several irssi features for Purple
- * Copyright (C) 2005-2007 Gary Kramlich <grim@reaperworld.com>
- * Copyright (C) 2007 John Bailey <rekkanoryo@rekkanoryo.org>
+ * Copyright (C) 2005-2008 Gary Kramlich <grim@reaperworld.com> + * Copyright (C) 2006-2008 John Bailey <rekkanoryo@rekkanoryo.org> + * Copyright (C) 2006-2008 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net> * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
@@ -19,6 +20,7 @@
+/* If you can't figure out what this line is for, DON'T TOUCH IT. */ #include "../common/pp_internal.h"
--- a/irssi/window.h Tue Oct 09 06:21:19 2007 -0400
+++ b/irssi/window.h Mon Mar 24 05:08:00 2008 -0400
@@ -1,7 +1,8 @@
* irssi - Implements several irssi features for Purple
- * Copyright (C) 2005-2007 Gary Kramlich <grim@reaperworld.com>
- * Copyright (C) 2007 John Bailey <rekkanoryo@rekkanoryo.org>
+ * Copyright (C) 2005-2008 Gary Kramlich <grim@reaperworld.com> + * Copyright (C) 2006-2008 John Bailey <rekkanoryo@rekkanoryo.org> + * Copyright (C) 2006-2008 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net> * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
--- a/lastseen/lastseen.c Tue Oct 09 06:21:19 2007 -0400
+++ b/lastseen/lastseen.c Mon Mar 24 05:08:00 2008 -0400
@@ -1,6 +1,6 @@
* Last Seen - Record when a buddy was last seen
- * Copyright (C) 2004 Stu Tomlinson <stu@nosnilmot.com>
+ * Copyright (C) 2004-2008 Stu Tomlinson <stu@nosnilmot.com> * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -17,6 +17,7 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
+/* If you can't figure out what this line is for, DON'T TOUCH IT. */ #include "../common/pp_internal.h"
--- a/listhandler/aim_blt_files.c Tue Oct 09 06:21:19 2007 -0400
+++ b/listhandler/aim_blt_files.c Mon Mar 24 05:08:00 2008 -0400
@@ -1,6 +1,6 @@
- * Copyright (C) 2003-2005
+ * Copyright (C) 2003-2008 * See ../AUTHORS for a list of all authors
* listhandler: Provides importing, exporting, and copying functions
--- a/listhandler/aim_blt_files.h Tue Oct 09 06:21:19 2007 -0400
+++ b/listhandler/aim_blt_files.h Mon Mar 24 05:08:00 2008 -0400
@@ -1,6 +1,6 @@
- * Copyright (C) 2003-2005
+ * Copyright (C) 2003-2008 * See ../AUTHORS for a list of all authors
* listhandler: Provides importing, exporting, and copying functions
--- a/listhandler/alias_xml_files.c Tue Oct 09 06:21:19 2007 -0400
+++ b/listhandler/alias_xml_files.c Mon Mar 24 05:08:00 2008 -0400
@@ -1,6 +1,6 @@
- * Copyright (C) 2003-2005
+ * Copyright (C) 2003-2008 * See ../AUTHORS for a list of all authors
* listhandler: Provides importing, exporting, and copying functions
--- a/listhandler/alias_xml_files.h Tue Oct 09 06:21:19 2007 -0400
+++ b/listhandler/alias_xml_files.h Mon Mar 24 05:08:00 2008 -0400
@@ -1,6 +1,6 @@
- * Copyright (C) 2003-2005
+ * Copyright (C) 2003-2008 * See ../AUTHORS for a list of all authors
* listhandler: Provides importing, exporting, and copying functions
--- a/listhandler/gen_xml_files.c Tue Oct 09 06:21:19 2007 -0400
+++ b/listhandler/gen_xml_files.c Mon Mar 24 05:08:00 2008 -0400
@@ -1,6 +1,6 @@
- * Copyright (C) 2003-2005
+ * Copyright (C) 2003-2008 * See ../AUTHORS for a list of all authors
* listhandler: Provides importing, exporting, and copying functions
--- a/listhandler/gen_xml_files.h Tue Oct 09 06:21:19 2007 -0400
+++ b/listhandler/gen_xml_files.h Mon Mar 24 05:08:00 2008 -0400
@@ -1,6 +1,6 @@
- * Copyright (C) 2003-2005
+ * Copyright (C) 2003-2008 * See ../AUTHORS for a list of all authors
* listhandler: Provides importing, exporting, and copying functions
--- a/listhandler/lh_util.c Tue Oct 09 06:21:19 2007 -0400
+++ b/listhandler/lh_util.c Mon Mar 24 05:08:00 2008 -0400
@@ -1,6 +1,6 @@
- * Copyright (C) 2003-2005
+ * Copyright (C) 2003-2008 * See ../AUTHORS for a list of all authors
* listhandler: Provides importing, exporting, and copying functions
--- a/listhandler/lh_util.h Tue Oct 09 06:21:19 2007 -0400
+++ b/listhandler/lh_util.h Mon Mar 24 05:08:00 2008 -0400
@@ -1,6 +1,6 @@
- * Copyright (C) 2003-2005
+ * Copyright (C) 2003-2008 * See ../AUTHORS for a list of all authors
* listhandler: Provides importing, exporting, and copying functions
--- a/listhandler/listhandler.c Tue Oct 09 06:21:19 2007 -0400
+++ b/listhandler/listhandler.c Mon Mar 24 05:08:00 2008 -0400
@@ -1,6 +1,6 @@
- * Copyright (C) 2003-2005
+ * Copyright (C) 2003-2008 * See ../AUTHORS for a list of all authors
* listhandler: Provides importing, exporting, and copying functions
--- a/listhandler/listhandler.h Tue Oct 09 06:21:19 2007 -0400
+++ b/listhandler/listhandler.h Mon Mar 24 05:08:00 2008 -0400
@@ -1,6 +1,6 @@
- * Copyright (C) 2003-2005
+ * Copyright (C) 2003-2008 * See ../AUTHORS for a list of all authors
* listhandler: Provides importing, exporting, and copying functions
@@ -21,6 +21,7 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
+/* If you can't figure out what this line is for, DON'T TOUCH IT. */ #include "../common/pp_internal.h"
--- a/listhandler/migrate.c Tue Oct 09 06:21:19 2007 -0400
+++ b/listhandler/migrate.c Mon Mar 24 05:08:00 2008 -0400
@@ -1,6 +1,6 @@
- * Copyright (C) 2003-2005
+ * Copyright (C) 2003-2008 * See ../AUTHORS for a list of all authors
* listhandler: Provides importing, exporting, and copying functions
--- a/listhandler/migrate.h Tue Oct 09 06:21:19 2007 -0400
+++ b/listhandler/migrate.h Mon Mar 24 05:08:00 2008 -0400
@@ -1,6 +1,6 @@
- * Copyright (C) 2003-2005
+ * Copyright (C) 2003-2008 * See ../AUTHORS for a list of all authors
* listhandler: Provides importing, exporting, and copying functions
--- a/listhandler/purple_blist_xml.c Tue Oct 09 06:21:19 2007 -0400
+++ b/listhandler/purple_blist_xml.c Mon Mar 24 05:08:00 2008 -0400
@@ -1,6 +1,6 @@
- * Copyright (C) 2003-2005
+ * Copyright (C) 2003-2008 * See ../AUTHORS for a list of all authors
* listhandler: Provides importing, exporting, and copying functions
--- a/listhandler/purple_blist_xml.h Tue Oct 09 06:21:19 2007 -0400
+++ b/listhandler/purple_blist_xml.h Mon Mar 24 05:08:00 2008 -0400
@@ -1,6 +1,6 @@
- * Copyright (C) 2003-2005
+ * Copyright (C) 2003-2008 * See ../AUTHORS for a list of all authors
* listhandler: Provides importing, exporting, and copying functions
--- a/m4/pluginpack.m4 Tue Oct 09 06:21:19 2007 -0400
+++ b/m4/pluginpack.m4 Mon Mar 24 05:08:00 2008 -0400
@@ -37,7 +37,7 @@
- if test -f "$d/Makefile.am" -a ! "$d" = "$srcdir/common" -a ! "$d" = "$srcdir/doc" -a ! "$d" = "$srcdir/m4" -a ! -f "$d/.abusive" -a ! -f "$d/.build" -a ! -f "$d/.incomplete" ; then
+ if test -f "$d/Makefile.am" -a ! "$d" = "$srcdir/common" -a ! "$d" = "$srcdir/doc" -a ! "$d" = "$srcdir/m4" -a ! -f "$d/configure" -a ! -f "$d/.abusive" -a ! -f "$d/.build" -a ! -f "$d/.incomplete" ; then *** Plugin Directory $d is misconfigured
@@ -96,29 +96,64 @@
dnl #######################################################################
AC_HELP_STRING([--with-plugins], [what plugins to build]),
dnl #######################################################################
dnl # Now determine which ones have been selected
dnl #######################################################################
- if test "x$with_plugins" = "xdefault" ; then
- tmp_SUB="$PP_AVAILABLE"
- exp_plugins=`echo "$with_plugins" | sed 's/,/ /g'`
- for p in "$PP_AVAILABLE $PP_ABUSIVE"; do
- for r in $exp_plugins; do
- if test x"$r" = x"$p" ; then
+ case "$with_plugins" in + PP_FINCH_BUILD="$PP_FINCH_ABUSIVE $PP_FINCH_BUILD" + PP_PIDGIN_BUILD="$PP_PIDGIN_ABUSIVE $PP_PIDGIN_BUILD" + PP_PURPLE_BUILD="$PP_PURPLE_ABUSIVE $PP_PURPLE_BUILD" + dnl # we don't do anything if the defaults are selected, they're + dnl # already set up :) + dnl # clear out the build variables + dnl # turn the with plugins variable into a space delimited list + exp_plugins=`echo "$with_plugins" | sed 's/,/ /g'` + dnl # loop through the with plugins list and update the build variables + dnl # as we find the plugins in each type. + PP_FINCH_BUILD="$PP_FINCH_BUILD $p" + PP_PIDGIN_BUILD="$PP_PIDGIN_BUILD $p" + PP_PURPLE_BUILD="$PP_PURPLE_BUILD $p"
- dnl # remove duplicates
- PP_BUILD=`echo $tmp_SUB | awk '{for (i = 1; i <= NF; i++) { print $i } }' | sort | uniq | xargs echo `
- dnl # add the abusive plugins to the dist
- PP_DIST="$PP_AVAILABLE $PP_ABUSIVE"
+ PP_FINCH_BUILD=`echo $PP_FINCH_BUILD | awk '{for (i = 1; i <=NF; i++) { print $i } }' | sort | uniq | xargs echo` + PP_PIDGIN_BUILD=`echo $PP_PIDGIN_BUILD | awk '{for (i = 1; i <=NF; i++) { print $i } } ' | sort | uniq | xargs echo` + PP_PURPLE_BUILD=`echo $PP_PURPLE_BUILD | awk '{for (i = 1; i <=NF; i++) { print $i } } ' | sort | uniq | xargs echo` dnl #######################################################################
dnl # substitue our sub dirs
--- a/menuconfig Tue Oct 09 06:21:19 2007 -0400
+++ b/menuconfig Mon Mar 24 05:08:00 2008 -0400
@@ -17,10 +17,11 @@
# build the list of plugins to pass to dialog
-AVAILABLE_PLUGINS=`find -name .plugin | cut -d/ -f2 | sort`
+AVAILABLE_PLUGINS=`find -regextype posix-extended -regex '^.+/\.(finch|pidgin|purple)-plugin' | \ for P in ${AVAILABLE_PLUGINS}
- if [ -f ${P}/.abusive ] ; then
+ if [ -f ${P}/.abusive -o -f ${P}/.incomplete ] ; then @@ -41,6 +42,7 @@
show_dialog --single-quoted --file ${PC_CONFIG} 2>${PC_RESULT}
+ rm -f ${PC_CONFIG} ${PC_RESULT} @@ -48,9 +50,11 @@
rm -f ${PC_CONFIG} ${PC_RESULT}
if [ -f configure.args ] ; then
- CONFIGURE_ARGS=`cat configure.args`
+echo "Running configure with '${CONFIGURE_ARGS} --with-plugins=${PLUGINS}" ./configure ${CONFIGURE_ARGS} --with-plugins=${PLUGINS}
--- a/mkinstalldirs Tue Oct 09 06:21:19 2007 -0400
+++ b/mkinstalldirs Mon Mar 24 05:08:00 2008 -0400
@@ -1,21 +1,33 @@
# mkinstalldirs --- make directory hierarchy
-# Author: Noah Friedman <friedman@prep.ai.mit.edu>
+scriptversion=2005-06-29.22 +# Original author: Noah Friedman <friedman@prep.ai.mit.edu>
+# This file is maintained in Automake, please report +# bugs to <bug-automake@gnu.org> or send patches to +# <automake-patches@gnu.org>.
-Usage: mkinstalldirs [-h] [--help] [-m mode] dir ..."
+Usage: mkinstalldirs [-h] [--help] [--version] [-m MODE] DIR ... +Create each directory DIR (with mode MODE, if specified), including all +leading file name components. +Report bugs to <bug-automake@gnu.org>." # process command line arguments
-h | --help | --h*) # -h for help
@@ -23,6 +35,10 @@
+ echo "$0 $scriptversion" --) # stop option processing
@@ -50,30 +66,58 @@
+# Solaris 8's mkdir -p isn't thread-safe. If you mkdir -p a/b and +# mkdir -p a/c at the same time, both will detect that a is missing, +# one will create a, then the other will try to create a and die with +# a "File exists" error. This is a problem when calling mkinstalldirs +# from a parallel make. We use --version in the probe to restrict +# ourselves to GNU mkdir, which is thread-safe. - if mkdir -p -- . 2>/dev/null; then
+ if mkdir -p --version . >/dev/null 2>&1 && test ! -d ./--version; then + # On NextStep and OpenStep, the `mkdir' command does not + # recognize any option. It will interpret all options as + # directories to create, and then abort because `.' already + test -d ./-p && rmdir ./-p + test -d ./--version && rmdir ./--version - if mkdir -m "$dirmode" -p -- . 2>/dev/null; then
+ if mkdir -m "$dirmode" -p --version . >/dev/null 2>&1 && + test ! -d ./--version; then echo "mkdir -m $dirmode -p -- $*"
exec mkdir -m "$dirmode" -p -- "$@"
+ # Clean up after NextStep and OpenStep mkdir. + for d in ./-m ./-p ./--version "./$dirmode"; - set fnord `echo ":$file" | sed -ne 's/^:\//#/;s/^://;s/\// /g;s/^#/\//;p'`
+ test "x$d" = x && continue -*) pathcomp=./$pathcomp ;;
@@ -84,21 +128,21 @@
mkdir "$pathcomp" || lasterr=$?
if test ! -d "$pathcomp"; then
- if test ! -z "$dirmode"; then
+ if test ! -z "$dirmode"; then echo "chmod $dirmode $pathcomp"
- chmod "$dirmode" "$pathcomp" || lasterr=$?
+ chmod "$dirmode" "$pathcomp" || lasterr=$? - if test ! -z "$lasterr"; then
+ if test ! -z "$lasterr"; then
@@ -107,5 +151,8 @@
+# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" -# mkinstalldirs ends here
--- a/mystatusbox/mystatusbox.c Tue Oct 09 06:21:19 2007 -0400
+++ b/mystatusbox/mystatusbox.c Mon Mar 24 05:08:00 2008 -0400
@@ -1,6 +1,6 @@
* mystatusbox - Show/Hide the peraccount statusboxes
+ * Copyright (C) 2005-2008 * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
@@ -18,6 +18,7 @@
+/* If you can't figure out what this line is for, DON'T TOUCH IT. */ #include "../common/pp_internal.h"
#define PLUGIN_ID "gtk-plugin_pack-mystatusbox"
Binary file napster/16/napster.png has changed
Binary file napster/22/napster.png has changed
Binary file napster/48/napster.png has changed
--- a/napster/Makefile.am Tue Oct 09 06:21:19 2007 -0400
+++ b/napster/Makefile.am Mon Mar 24 05:08:00 2008 -0400
@@ -1,9 +1,10 @@
napsterdir = $(PURPLE_LIBDIR)
@@ -21,19 +22,14 @@
-napsterpix16x16dir=$(PIDGIN_PIXMAPSDIR)/protocols/16
-napsterpix16x16_DATA=napster16x16.png
-napsterpix22x22dir=$(PIDGIN_PIXMAPSDIR)/protocols/22
-napsterpix22x22_DATA=napster22x22.png
+napsterpix16dir=$(PIDGIN_PIXMAPSDIR)/protocols/16 +napsterpix16_DATA=16/napster.png -napsterpix48x48dir=$(PIDGIN_PIXMAPSDIR)/protocols/48
-napsterpix48x48_DATA=napster48x48.png
+napsterpix22dir=$(PIDGIN_PIXMAPSDIR)/protocols/22 +napsterpix22_DATA=22/napster.png
- mv $(PIDGIN_PIXMAPSDIR)/protocols/16/napster16x16.png $(PIDGIN_PIXMAPSDIR)//protocols/16/napster.png
- mv $(PIDGIN_PIXMAPSDIR)/protocols/22/napster22x22.png $(PIDGIN_PIXMAPSDIR)//protocols/22/napster.png
- mv $(PIDGIN_PIXMAPSDIR)/protocols/48/napster48x48.png $(PIDGIN_PIXMAPSDIR)//protocols/48/napster.png
+napsterpix48dir=$(PIDGIN_PIXMAPSDIR)/protocols/48 +napsterpix48_DATA=48/napster.png @@ -45,3 +41,5 @@
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/napster/Makefile.mingw Mon Mar 24 05:08:00 2008 -0400
@@ -0,0 +1,11 @@
+# Description: Makefile for napster plugin. +include $(PP_TOP)/win_pp.mak --- a/napster/napster.c Tue Oct 09 06:21:19 2007 -0400
+++ b/napster/napster.c Mon Mar 24 05:08:00 2008 -0400
@@ -4,7 +4,8 @@
* Copyright (C) 2000-2001, Rob Flynn <rob@marko.net>
* Assimilated for inclusion in the Plugin Pack:
- * Copyright (C) 2006, Gary Kramlich <grim@reaperworld.com>
+ * Copyright (C) 2006-2008 Gary Kramlich <grim@reaperworld.com> + * Copyright (C) 2007 John Bailey <rekkanoryo@rekkanoryo.org> * This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -21,6 +22,7 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+/* If you can't figure out what this line is for, DON'T TOUCH IT. */ #include "../common/pp_internal.h"
@@ -269,14 +271,17 @@
nap_callback(gpointer data, gint source, PurpleInputCondition condition) {
PurpleConnection *gc = data;
struct nap_data *ndata = gc->proto_data;
- PurpleAccount *account = purple_connection_get_account(gc);
- gchar *buf, *buf2, *buf3, **res;
- unsigned short header[2];
+ PurpleAccount *account = NULL; + PurpleConversation *c = NULL; + PurpleNotifyUserInfo *pnui = NULL; + gchar *buf = NULL, *buf2 = NULL, *buf3 = NULL, **res = NULL; + unsigned short header[2] = { 0 , 0 }; + account = purple_connection_get_account(gc); if (read(source, (void*)header, 4) != 4) {
purple_connection_error(gc, _("Unable to read header from server"));
@@ -292,7 +297,7 @@
int tmp = read(source, buf + i, len - i);
- buf = g_strdup_printf(_("Unable to read message from server: %s. Command is %hd, length is %hd."), strerror(errno), len, command);
+ buf = g_strdup_printf(_("Unable to read message from server: %s. Command is %hd, length is %hd."), strerror(errno), len, command); purple_connection_error(gc, buf);
@@ -441,7 +446,9 @@
/* XXX - Format is: "Elite" 37 " " "Active" 0 0 0 0 "purple 0.63cvs" 0 0 192.168.1.41 32798 0 unknown flounder */
res = g_strsplit(buf, " ", 2);
- purple_notify_userinfo(gc, res[0], res[1], NULL, NULL);
+ pnui = purple_notify_user_info_new(); + purple_notify_user_info_add_pair(pnui, _("Napster User Info:"), res[1]); + purple_notify_userinfo(gc, res[0], pnui, NULL, NULL); @@ -538,7 +545,7 @@
/* Write our signon data */
nap_write_packet(gc, 2, "%s %s 0 \"purple %s\" 0",
purple_account_get_username(gc->account),
- purple_connection_get_password(gc), VERSION);
+ purple_connection_get_password(gc), PP_VERSION); /* And set up the input watcher */
gc->inpa = purple_input_add(ndata->fd, PURPLE_INPUT_READ, nap_callback, gc);
@@ -580,14 +587,6 @@
-nap_list_emblems(PurpleBuddy *b, const char **se, const char **sw,
- const char **nw, const char **ne)
- if(!PURPLE_BUDDY_IS_ONLINE(b))
nap_status_types(PurpleAccount *account) {
@@ -639,7 +638,7 @@
NULL, /* protocol_options */
NO_BUDDY_ICONS, /* icon_spec */
nap_list_icon, /* list_icon */
- nap_list_emblems, /* list_emblems */
+ NULL, /* list_emblems */ nap_status_types, /* status_types */
@@ -694,9 +693,11 @@
NULL, /* offline_message */
NULL, /* whiteboard_prpl_ops */
+ NULL, /* roomlist_room_serialize */ + NULL, /* unregister_user */ + NULL, /* send_attention */ + NULL, /* get_attention_types */ @@ -704,21 +705,21 @@
- PURPLE_PLUGIN_PROTOCOL, /**< type */
+ PURPLE_PLUGIN_PROTOCOL, /**< type */ NULL, /**< ui_requirement */
NULL, /**< dependencies */
- PURPLE_PRIORITY_DEFAULT, /**< priority */
+ PURPLE_PRIORITY_DEFAULT, /**< priority */ "prpl-napster", /**< id */
- "Napster", /**< name */
- VERSION, /**< version */
+ N_("Napster"), /**< name */ + PP_VERSION, /**< version */ N_("NAPSTER Protocol Plugin"),
N_("NAPSTER Protocol Plugin"),
- PP_WEBSITE, /**< homepage */
+ PP_WEBSITE, /**< homepage */ @@ -738,16 +739,21 @@
init_plugin(PurplePlugin *plugin) {
PurpleAccountOption *option;
- option = purple_account_option_string_new(_("Server"), "server",
- prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
+ bindtextdomain(GETTEXT_PACKAGE, PP_LOCALEDIR); + bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); + option = purple_account_option_string_new(_("Server"), "server", NAP_SERVER); + prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); option = purple_account_option_int_new(_("Port"), "port", 8888);
- prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
+ prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); + info.description = _(info.description); + info.summary = _(info.summary); PURPLE_INIT_PLUGIN(napster, init_plugin, info);
Binary file napster/napster16x16.png has changed
Binary file napster/napster22x22.png has changed
Binary file napster/napster48x48.png has changed
--- a/nicksaid/nicksaid.c Tue Oct 09 06:21:19 2007 -0400
+++ b/nicksaid/nicksaid.c Mon Mar 24 05:08:00 2008 -0400
@@ -1,6 +1,6 @@
* Nicksaid - Record when someone said your nick in a chat.
+ * Copyright (C) 2006-2008 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net> * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
@@ -18,6 +18,7 @@
+/* If you can't figure out what this line is for, DON'T TOUCH IT. */ #include "../common/pp_internal.h"
#define PLUGIN_ID "gtk-plugin_pack-nicksaid"
@@ -221,7 +222,7 @@
NickSaid *said = list->data;
- g_string_append_printf(str, "%s\n", said->what);
+ g_string_append_printf(str, "%s<br/>\n", said->what); --- a/oldlogger/oldlogger.c Tue Oct 09 06:21:19 2007 -0400
+++ b/oldlogger/oldlogger.c Mon Mar 24 05:08:00 2008 -0400
@@ -1,6 +1,6 @@
* Old Logger - Re-implements the legacy, deficient, logging
- * Copyright (C) 2004 Stu Tomlinson <stu@nosnilmot.com>
+ * Copyright (C) 2004-2008 Stu Tomlinson <stu@nosnilmot.com> * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -17,6 +17,7 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
+/* If you can't figure out what this line is for, DON'T TOUCH IT. */ #include "../common/pp_internal.h"
#define OLDLOGGER_PLUGIN_ID "core-plugin_pack-oldlogger"
@@ -99,6 +100,7 @@
"Could not create log file %s\n", filename);
+ log->logger_data = NULL; data->filename = filename;
@@ -227,6 +229,11 @@
+ for (filename = guy; *filename != '\0'; filename++) { if (log->type == PURPLE_LOG_CHAT) {
chat = g_strdup_printf("%s.chat", guy);
@@ -254,6 +261,7 @@
purple_debug(PURPLE_DEBUG_ERROR, "log", "Could not create log file %s\n", filename);
+ log->logger_data = NULL; data->filename = filename;
@@ -336,6 +344,11 @@
+ for (filename = guy; *filename != '\0'; filename++) { if (log->type == PURPLE_LOG_CHAT) {
chat = g_strdup_printf("%s.chat", guy);
@@ -364,6 +377,7 @@
"Could not create log file %s\n", filename);
+ log->logger_data = NULL; data->filename = filename;
--- a/plonkers/plonkers.c Tue Oct 09 06:21:19 2007 -0400
+++ b/plonkers/plonkers.c Mon Mar 24 05:08:00 2008 -0400
@@ -1,6 +1,6 @@
- Purple-Plonkers - Manager the plonkers out in cyberland
- Copyright (C) 2005 Peter Lawler
+ Purple-Plonkers - Manage the plonkers out in cyberland + Copyright (C) 2005-2008 Peter Lawler Very loosely based on gxr, Copyright (C) 2004 Gary Kramlich
@@ -19,6 +19,7 @@
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
+/* If you can't figure out what this line is for, DON'T TOUCH IT. */ #include "../common/pp_internal.h"
--- a/plugin_pack.spec.in Tue Oct 09 06:21:19 2007 -0400
+++ b/plugin_pack.spec.in Mon Mar 24 05:08:00 2008 -0400
@@ -23,7 +23,8 @@
Source0: %{name}-%{version}.tar.bz2
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
-BuildRequires: pkgconfig, libtool, gettext, xmms-devel
+BuildRequires: pkgconfig, libtool, gettext +%{!?_without_xmms:BuildRequires: xmms-devel} BuildRequires: pidgin-devel >= %{pidgin_major_ver}.%{pidgin_minor_ver}, pidgin-devel < %{pidgin_next_major_ver}
%if "%{_vendor}" == "MandrakeSoft" || "%{_vendor}" == "Mandrakesoft"
BuildRequires: libgtk+2.0_0-devel
@@ -36,6 +37,7 @@
Summary: Plugin Pack for Pidgin
Group: Applications/Internet
Requires: pidgin >= %{pidgin_major_ver}.%{pidgin_build_minor_ver}, pidgin < %{pidgin_next_major_ver}
+Requires: purple-plugin_pack = %{version} All the other plugins for all libpurple derived clients
@@ -74,6 +76,12 @@
+* Sat Mar 01 2008 Stu Tomlinson <stu@nosnilmot.com> +- make the pidgin plugin pack depend on the purple plugin pack +* Sat Oct 27 2007 Stu Tomlinson <stu@nosnilmot.com> +- Add --without xmms option to build without xmms plugin * Mon Apr 30 2007 Stu Tomlinson <stu@nosnilmot.com>
- Update for the rename of Gaim to Pidgin
- New URL for our new website
--- a/po/POTFILES.in Tue Oct 09 06:21:19 2007 -0400
+++ b/po/POTFILES.in Mon Mar 24 05:08:00 2008 -0400
@@ -5,17 +5,17 @@
+buddytime/gtkbuddytime.c
+common/purple_template.c +dewysiwygification/dewysiwygification.c +enhancedhist/enhancedhist.c @@ -62,5 +62,10 @@
switchspell/switchspell.c
talkfilters/talkfilters.c
xchat-chats/xchat-chats.c
xchat-chats/xchat-chats.c
--- a/po/en_AU.po Tue Oct 09 06:21:19 2007 -0400
+++ b/po/en_AU.po Mon Mar 24 05:08:00 2008 -0400
@@ -8,7 +8,7 @@
"Project-Id-Version: plugin_pack 0.1cvs\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2007-10-09 00:11-0400\n"
+"POT-Creation-Date: 2007-10-22 00:43-0400\n" "PO-Revision-Date: 2005-11-21 10:36+1100\n"
"Last-Translator: Peter Lawler <trans@six-by-nine.com.au>\n"
"Language-Team: English/AU <trans@six-by-nine.com.au>\n"
@@ -16,6 +16,18 @@
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
+msgid "Archives buddy icons." +msgid "Enable this plugin to automatically archive all buddy icons." #: ../album/album-ui.c:293
"<span size='larger' weight='bold'>Unrecognized file type</span>\n"
@@ -92,6 +104,7 @@
#: ../album/album-ui.c:1142 ../broadcast/broadcast.c:117
#: ../buddytime/buddytime.c:163 ../groupmsg/groupmsg.c:111
+#: ../timelog/timelog.c:130 @@ -99,120 +112,113 @@
msgid "_View Buddy Icons"
-msgid "Archives buddy icons."
-msgid "Enable this plugin to automatically archive all buddy icons."
#. XXX: There should be a way to reset to the default/account-default autoreply
-#: ../autoreply/autoreply.c:221
+#: ../autoreply/autoreply.c:226 msgid "Set autoreply message for %s"
-#: ../autoreply/autoreply.c:223
-msgid "Set Autoreply Message"
-#: ../autoreply/autoreply.c:224
-"The following message will be sent to the buddy when the buddy sends you a "
-"message and autoreply is enabled."
#: ../autoreply/autoreply.c:228
+msgid "Set Autoreply Message" +#: ../autoreply/autoreply.c:229 +"The following message will be sent to the buddy when the buddy sends you a " +"message and autoreply is enabled." +#: ../autoreply/autoreply.c:233 -#: ../autoreply/autoreply.c:229 ../listhandler/aim_blt_files.c:301
-#: ../listhandler/aim_blt_files.c:465 ../listhandler/gen_xml_files.c:163
+#: ../autoreply/autoreply.c:234 ../listhandler/aim_blt_files.c:301 +#: ../listhandler/aim_blt_files.c:465 ../listhandler/alias_xml_files.c:210 +#: ../listhandler/alias_xml_files.c:269 ../listhandler/gen_xml_files.c:163 #: ../listhandler/gen_xml_files.c:355 ../listhandler/migrate.c:145
-#: ../listhandler/migrate.c:184
+#: ../listhandler/migrate.c:184 ../listhandler/purple_blist_xml.c:229 -#: ../autoreply/autoreply.c:245
+#: ../autoreply/autoreply.c:250 msgid "Set _Autoreply Message"
-#: ../autoreply/autoreply.c:258
+#: ../autoreply/autoreply.c:263 msgid "Autoreply message"
-#: ../autoreply/autoreply.c:357
+#: ../autoreply/autoreply.c:265 +msgid "Turn off autoreply" +#: ../autoreply/autoreply.c:370 msgid "Send autoreply messages when"
-#: ../autoreply/autoreply.c:361
+#: ../autoreply/autoreply.c:374 msgid "When my account is _away"
-#: ../autoreply/autoreply.c:365
+#: ../autoreply/autoreply.c:378 msgid "When my account is _idle"
-#: ../autoreply/autoreply.c:369
+#: ../autoreply/autoreply.c:382 -#: ../autoreply/autoreply.c:376
+#: ../autoreply/autoreply.c:389 -#: ../autoreply/autoreply.c:379
+#: ../autoreply/autoreply.c:392 -#: ../autoreply/autoreply.c:383
+#: ../autoreply/autoreply.c:396 msgid "Autoreply with status message"
-#: ../autoreply/autoreply.c:385
-#: ../autoreply/autoreply.c:387
-msgid "Always when there is a status message"
-#: ../autoreply/autoreply.c:389
-msgid "Only when there's no autoreply message"
-#: ../autoreply/autoreply.c:394
-msgid "Delay between autoreplies"
#: ../autoreply/autoreply.c:398
-msgid "_Minimum delay (mins)"
+#: ../autoreply/autoreply.c:400 +msgid "Always when there is a status message" #: ../autoreply/autoreply.c:402
+msgid "Only when there's no autoreply message" +#: ../autoreply/autoreply.c:407 +msgid "Delay between autoreplies" +#: ../autoreply/autoreply.c:411 +msgid "_Minimum delay (mins)" +#: ../autoreply/autoreply.c:415 msgid "Times to send autoreplies"
-#: ../autoreply/autoreply.c:406
+#: ../autoreply/autoreply.c:419 -#: ../autoreply/autoreply.c:464
+#: ../autoreply/autoreply.c:477 -#: ../autoreply/autoreply.c:465
+#: ../autoreply/autoreply.c:478 msgid "Autoreply for all the protocols"
-#: ../autoreply/autoreply.c:466
+#: ../autoreply/autoreply.c:479 "This plugin lets you set autoreply message for any protocol. You can set the "
"global autoreply message from the plugin options dialog. To set some "
@@ -221,13 +227,13 @@
"the `Advanced' tab of the account edit dialog."
-#: ../autoreply/autoreply.c:476
+#: ../autoreply/autoreply.c:489 "I am currently not available. Please leave your message, and I will get back "
"to you as soon as possible."
-#: ../autoreply/autoreply.c:481
+#: ../autoreply/autoreply.c:494 msgid "This is an autoreply: "
@@ -505,11 +511,13 @@
-#: ../buddytime/buddytime.c:256
+#: ../buddytime/buddytime.c:256 ../common/core_template.c:80 +#: ../common/gtk_template.c:84 -#: ../buddytime/buddytime.c:257
+#: ../buddytime/buddytime.c:257 ../common/core_template.c:81 +#: ../common/gtk_template.c:85 @@ -529,6 +537,10 @@
"FUNCTIONAL! IT IS USELESS!"
+#: ../common/core_template.c:79 ../common/gtk_template.c:83 #: ../convbadger/convbadger.c:192
msgid "Conversation Badger"
@@ -537,6 +549,20 @@
msgid "Badges conversations with the protocol icon."
+#: ../dewysiwygification/dewysiwygification.c:99 +msgid "DeWYSIWYGification Plugin" +#: ../dewysiwygification/dewysiwygification.c:101 +msgid "Lets you type in HTML without it being escaped to entities." +#: ../dewysiwygification/dewysiwygification.c:102 +"Lets you type in HTML without it being escaped to entities. This will not " +"work well for some protocols. Use \"<\" for a literal \"<\"." msgid "dice [dice] [sides]: rolls dice number of sides sided dice"
@@ -568,33 +594,53 @@
msgid "Show the old topic when the topic in a chat room changes."
-#: ../eight_ball/eight_ball.c:134
+#: ../eight_ball/eight_ball.c:135 msgid "8ball: sends a random 8ball message"
-#: ../eight_ball/eight_ball.c:135
-msgid "sgball: sends a random Stargate Ball message"
#: ../eight_ball/eight_ball.c:136
+msgid "sgball: sends a random Stargate Ball message" +#: ../eight_ball/eight_ball.c:137 msgid "fullcrap: sends random fooling blabber"
-#: ../eight_ball/eight_ball.c:209
#: ../eight_ball/eight_ball.c:210
-msgid "Provides Magic 8-ball like functionality"
#: ../eight_ball/eight_ball.c:211
+msgid "Provides Magic 8-ball like functionality" +#: ../eight_ball/eight_ball.c:212 "Provides Magic 8-ball like functionality with the /8ball command, as well as "
"similar functionality for common Stargate words or phrases with the /sg-ball "
+#: ../findip/findip.c:60 +msgid "Looked up IP: 127.0.0.1\n" +#: ../findip/findip.c:64 +msgid "Yo! What's your IP?" +#: ../findip/findip.c:83 +msgid "Looking up the IP ...\n" +#: ../findip/findip.c:96 +#: ../findip/findip.c:124 +msgid "Notify the user that you are trying to get the IP" msgid "Outputs the results of flipping a coin"
@@ -885,44 +931,51 @@
"Please do not use if you have amnesia."
-#. specify our help string and register our command
-#: ../irc-more/irc-more.c:212
-msgid "notice target message: Send a notice to the specified target."
-#. Alphabetize the option label strings
-#: ../irc-more/irc-more.c:232
-msgid "CTCP Version reply"
-#: ../irc-more/irc-more.c:235
-msgid "Default Quit Message"
-#: ../irc-more/irc-more.c:238
-msgid "Default Part Message"
-#: ../irc-more/irc-more.c:241
-msgid "User Modes On Connect"
-#: ../irc-more/irc-more.c:264
-msgid "Seconds to wait before rejoining"
-#: ../irc-more/irc-more.c:325
-#: ../irc-more/irc-more.c:326
-msgid "Adds additional IRC features."
-#: ../irc-more/irc-more.c:327
+#: ../infopane/infopane.c:148 ../infopane/infopane.c:262 +msgid "Libpurple and Pidgin are too old!\n" +#: ../infopane/infopane.c:149 +msgid "Incompatible Plugin" +#: ../infopane/infopane.c:150 +msgid "You need to update Pidgin!" +#: ../infopane/infopane.c:151 -"Adds additional IRC features, including a customizable quit message, a "
-"customizable CTCP VERSION reply, and the /notice command for notices."
+"This plugin is incompatible with the running version of Pidgin and Libpurple " +"because it is too old. Please upgrade to the newest version of Pidgin." +#. XXX: Is there a better way than this? There really should be. +#: ../infopane/infopane.c:183 +msgid "Position of the infopane ('top', 'bottom' or 'none')" +#: ../infopane/infopane.c:187 +msgid "Show icon in the tabs" +#: ../infopane/infopane.c:191 +msgid "Always show the tab" +#: ../infopane/infopane.c:251 +msgid "Infopane Options" +#: ../infopane/infopane.c:252 ../infopane/infopane.c:253 +msgid "Allow customizing the details information in conversation windows." +#: ../infopane/infopane.c:264 +msgid "Incompatible Plugin! - Check plugin details!" +#: ../infopane/infopane.c:265 ../infopane/infopane.c:266 +msgid "This plugin is NOT compatible with this version of Pidgin!" #: ../irchelper/irchelper.c:937
@@ -998,6 +1051,50 @@
"- Suppression of various useless messages"
+#. specify our help string and register our command +#: ../irc-more/irc-more.c:219 +msgid "notice target message: Send a notice to the specified target." +#. Alphabetize the option label strings +#: ../irc-more/irc-more.c:239 +msgid "CTCP Version reply" +#: ../irc-more/irc-more.c:242 +msgid "Default Quit Message" +#: ../irc-more/irc-more.c:245 +msgid "Default Part Message" +#: ../irc-more/irc-more.c:248 +msgid "Set User Modes On Connect" +#: ../irc-more/irc-more.c:251 +msgid "Unset User Modes On Connect" +#: ../irc-more/irc-more.c:273 +msgid "Seconds to wait before rejoining" +#: ../irc-more/irc-more.c:334 +#: ../irc-more/irc-more.c:335 +msgid "Adds additional IRC features." +#: ../irc-more/irc-more.c:336 +"Adds additional IRC features, including a customizable quit message, a " +"customizable CTCP VERSION reply, and the /notice command for notices." #: ../irssi/datechange.c:79
msgid "Day changed to %s"
@@ -1139,9 +1236,10 @@
#: ../listhandler/aim_blt_files.c:287 ../listhandler/aim_blt_files.c:453
+#: ../listhandler/alias_xml_files.c:203 ../listhandler/alias_xml_files.c:260 #: ../listhandler/gen_xml_files.c:148 ../listhandler/gen_xml_files.c:341
#: ../listhandler/migrate.c:129 ../listhandler/migrate.c:169
-#: ../schedule/pidgin-schedule.c:283
+#: ../listhandler/purple_blist_xml.c:221 ../schedule/pidgin-schedule.c:283 @@ -1150,11 +1248,13 @@
msgid "List Handler: Importing"
-#: ../listhandler/aim_blt_files.c:298 ../listhandler/gen_xml_files.c:160
+#: ../listhandler/aim_blt_files.c:298 ../listhandler/alias_xml_files.c:266 +#: ../listhandler/gen_xml_files.c:160 msgid "Choose the account to import to:"
-#: ../listhandler/aim_blt_files.c:299 ../listhandler/gen_xml_files.c:161
+#: ../listhandler/aim_blt_files.c:299 ../listhandler/alias_xml_files.c:267 +#: ../listhandler/gen_xml_files.c:161 ../listhandler/purple_blist_xml.c:228 @@ -1167,11 +1267,13 @@
msgid "List Handler: Exporting"
-#: ../listhandler/aim_blt_files.c:464 ../listhandler/gen_xml_files.c:354
+#: ../listhandler/aim_blt_files.c:464 ../listhandler/alias_xml_files.c:209 +#: ../listhandler/gen_xml_files.c:354 msgid "Choose the account to export from:"
-#: ../listhandler/aim_blt_files.c:465 ../listhandler/gen_xml_files.c:355
+#: ../listhandler/aim_blt_files.c:465 ../listhandler/alias_xml_files.c:210 +#: ../listhandler/gen_xml_files.c:355 @@ -1179,24 +1281,29 @@
msgid "Choose An AIM .blt File To Import"
+#: ../listhandler/alias_xml_files.c:182 +msgid "Save Generic .alist File" #. and finally we can create the request
-#: ../listhandler/gen_xml_files.c:159
+#: ../listhandler/alias_xml_files.c:208 ../listhandler/gen_xml_files.c:353 +msgid "Listhandler - Exporting" +#. and finally we can create the request +#: ../listhandler/alias_xml_files.c:265 ../listhandler/gen_xml_files.c:159 +#: ../listhandler/purple_blist_xml.c:226 msgid "Listhandler - Importing"
+#: ../listhandler/alias_xml_files.c:296 ../listhandler/gen_xml_files.c:366 +msgid "Choose A Generic Buddy List File To Import" #: ../listhandler/gen_xml_files.c:315
msgid "Save Generic .blist File"
-#. and finally we can create the request
-#: ../listhandler/gen_xml_files.c:353
-msgid "Listhandler - Exporting"
-#: ../listhandler/gen_xml_files.c:366
-msgid "Choose A Generic Buddy List File To Import"
#: ../listhandler/listhandler.c:39
msgid "Copy Buddies From One Account to Another"
@@ -1266,6 +1373,14 @@
+#: ../listhandler/purple_blist_xml.c:227 +msgid "Choose the account whose buddy list you wish to restore:" +#: ../listhandler/purple_blist_xml.c:253 +msgid "Choose a Libpurple blist.xml File To Import" #: ../mystatusbox/mystatusbox.c:376
@@ -1981,5 +2096,74 @@
"pansy, pirate, postmodern, redneck, valspeak, and warez."
+#: ../timelog/log-widget.c:224 +msgid "Conversation in %s on %s" +#: ../timelog/log-widget.c:226 +msgid "Conversation with %s on %s" +#: ../timelog/log-widget.c:288 +msgid "No logs were found" +#: ../timelog/range-widget.c:226 +#: ../timelog/range-widget.c:230 +#: ../timelog/range-widget.c:330 +msgid "Select Time Range" +#: ../timelog/timelog.c:128 +msgid "Select account to view logs for:" +#: ../timelog/timelog.c:129 +#: ../timelog/timelog.c:139 +msgid "Select Account/Time"