Merge pep modules & use events
authorMyhailo Danylenko <isbear@ukrpost.net>
Sun, 20 May 2012 22:15:51 +0300
changeset 29 23fa36d480fb
parent 28 c035fbbab184
child 30 a66ed0454ca8
Merge pep modules & use events * merge mood, tune, activity, geoloc, avatar * split them into functionality/ui * tune, geoloc: fix timeout bug * geoloc: fix guard unregistering * tune, mood: fix memleaking delayed info * mood: fix non-publish of offline-delayed data * activity: unregister reply handler on disconnect * fix silly copyright typo * update hgignore * v0.0.3
.hgignore
CMakeLists.txt
README
TODO
activity.c
activity.h
avv/activity.in
avv/geoloc.in
avv/mood.in
avv/pep.in
avv/pep_activity.in
avv/pep_geoloc.in
avv/pep_mood.in
avv/pep_tune.in
avv/tune.in
config.h.in
doc/activities.txt
doc/api.mdwn
doc/moods.txt
geoloc.c
geoloc.h
help/en/hlp_activity.txt
help/en/hlp_geoloc.txt
help/en/hlp_mood.txt
help/en/hlp_tune.txt
mood.c
mood.h
pep.avv.in
pep.c
pep.rc
pep_activity.c
pep_geoloc.c
pep_mood.c
pep_tune.c
tune.c
tune.h
--- a/.hgignore	Wed Jun 29 19:25:16 2011 +0300
+++ b/.hgignore	Sun May 20 22:15:51 2012 +0300
@@ -1,25 +1,7 @@
-# ignore generated by libtool files (no more necessary)
-.*\.a
-.*\.o
-.*\.lo
-.*\.la
-.*\.so
-.*\.lai
-.*\.so\..*
 # ignore backup files
-.*~
-# ignore docs: they are not important and apiref is generated on the fly
-.*\.html
-# ignore generated makefile
-Makefile
-# ignore cmake files
-.*\.cmake
-CMake.*
-CPack.*
-# ignore generated header
-config\.h
-# ignore resulting bundles
-.*\.deb
-.*\.tar\.bz2
+~$
+\.swp$
+\.orig$
+\.rej$
 # ignore build dir
-build
+^build$
--- a/CMakeLists.txt	Wed Jun 29 19:25:16 2011 +0300
+++ b/CMakeLists.txt	Sun May 20 22:15:51 2012 +0300
@@ -16,7 +16,7 @@
 
 cmake_minimum_required(VERSION 2.6) 
 project(pep C) 
-set(PROJECT_VERSION "0.0.1")
+set(PROJECT_VERSION "0.0.3")
 
 ## User settable options
  
@@ -25,31 +25,61 @@
 pkg_check_modules(GLIB REQUIRED glib-2.0) 
 pkg_check_modules(LM REQUIRED loudmouth-1.0) 
 pkg_check_modules(MCABBER REQUIRED mcabber)
+include(CheckSymbolExists)
+set(CMAKE_REQUIRED_INCLUDES ${LM_INCLUDE_DIRS})
+set(CMAKE_REQUIRED_LIBRARIES ${LM_LIBRARIES})
+set(CMAKE_REQUIRED_FLAGS ${LM_LDFLAGS} ${LM_CFLAGS})
+check_symbol_exists(lm_connection_unregister_reply_handler loudmouth/loudmouth.h HAVE_LM_CONNECTION_UNREGISTER_REPLY_HANDLER)
 link_directories(${GLIB_LIBRARY_DIRS}
 				 ${LM_LIBRARY_DIRS}
 				 ${MCABBER_LIBRARY_DIRS})
 
 ## Target definitions
-add_library(pep MODULE pep.c) 
+add_library(pep          MODULE pep.c pep.h) 
+add_library(mood         MODULE mood.c mood.h) 
+add_library(pep_mood     MODULE pep_mood.c mood.h pep.h) 
+add_library(tune         MODULE tune.c tune.h) 
+add_library(pep_tune     MODULE pep_tune.c tune.h pep.h) 
+add_library(geoloc       MODULE geoloc.c geoloc.h) 
+add_library(pep_geoloc   MODULE pep_geoloc.c geoloc.h pep.h) 
+add_library(activity     MODULE activity.c activity.h) 
+add_library(pep_activity MODULE pep_activity.c activity.h pep.h) 
 
 ## Compiler setup
+if(DEBUG)
+		set(DEBUG_COMPILE_FLAGS "-g")
+endif()
 configure_file(config.h.in config.h)
 include_directories(SYSTEM ${GLIB_INCLUDE_DIRS} 
                     ${LM_INCLUDE_DIRS}
 					${MCABBER_INCLUDE_DIRS})
-target_link_libraries(pep ${GLIB_LIBRARIES} 
-					  ${LM_LIBRARIES}
-					  ${MCABBER_LIBRARIES})
+target_link_libraries(pep          ${GLIB_LIBRARIES} ${LM_LIBRARIES} ${MCABBER_LIBRARIES})
+target_link_libraries(mood         ${GLIB_LIBRARIES}                 ${MCABBER_LIBRARIES})
+target_link_libraries(pep_mood     ${GLIB_LIBRARIES} ${LM_LIBRARIES} ${MCABBER_LIBRARIES})
+target_link_libraries(tune         ${GLIB_LIBRARIES}                 ${MCABBER_LIBRARIES})
+target_link_libraries(pep_tune     ${GLIB_LIBRARIES} ${LM_LIBRARIES} ${MCABBER_LIBRARIES})
+target_link_libraries(geoloc       ${GLIB_LIBRARIES}                 ${MCABBER_LIBRARIES})
+target_link_libraries(pep_geoloc   ${GLIB_LIBRARIES} ${LM_LIBRARIES} ${MCABBER_LIBRARIES})
+target_link_libraries(activity     ${GLIB_LIBRARIES}                 ${MCABBER_LIBRARIES})
+target_link_libraries(pep_activity ${GLIB_LIBRARIES} ${LM_LIBRARIES} ${MCABBER_LIBRARIES})
 include_directories(${pep_SOURCE_DIR} 
                     ${pep_BINARY_DIR})
-set_target_properties(pep PROPERTIES COMPILE_FLAGS "-Wall")
+set_target_properties(pep          PROPERTIES COMPILE_FLAGS "-Wall ${DEBUG_COMPILE_FLAGS}")
+set_target_properties(mood         PROPERTIES COMPILE_FLAGS "-Wall ${DEBUG_COMPILE_FLAGS}")
+set_target_properties(pep_mood     PROPERTIES COMPILE_FLAGS "-Wall ${DEBUG_COMPILE_FLAGS}")
+set_target_properties(tune         PROPERTIES COMPILE_FLAGS "-Wall ${DEBUG_COMPILE_FLAGS}")
+set_target_properties(pep_tune     PROPERTIES COMPILE_FLAGS "-Wall ${DEBUG_COMPILE_FLAGS}")
+set_target_properties(geoloc       PROPERTIES COMPILE_FLAGS "-Wall ${DEBUG_COMPILE_FLAGS}")
+set_target_properties(pep_geoloc   PROPERTIES COMPILE_FLAGS "-Wall ${DEBUG_COMPILE_FLAGS}")
+set_target_properties(activity     PROPERTIES COMPILE_FLAGS "-Wall ${DEBUG_COMPILE_FLAGS}")
+set_target_properties(pep_activity PROPERTIES COMPILE_FLAGS "-Wall ${DEBUG_COMPILE_FLAGS}")
 
 ## Packaging information
 set(CPACK_PACKAGE_NAME libmcabber-pep)
 set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION})
 set(CPACK_PACKAGE_VENDOR "IsBear")
 set(CPACK_PACKAGE_CONTACT "Myhailo Danylenko <isbear@ukrpost.net>")
-set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Common PEP listener module")
+set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "PEP modules")
 set(CPACK_RESOURCE_FILE_LICENSE ${pep_SOURCE_DIR}/COPYING)
 set(CPACK_SOURCE_GENERATOR TBZ2)
 set(CPACK_GENERATOR DEB CACHE TEXT "Binary package generator, eg DEB, RPM, TGZ, NSIS...")
@@ -64,12 +94,22 @@
 set(CPACK_SOURCE_IGNORE_FILES "/\\\\..*;\\\\.swp;~$;/build/;\\\\.tar\\\\.;\\\\.deb;\\\\.so")
 include(CPack)
 
-configure_file(pep.avv.in pep.avv)
+configure_file(avv/pep.in          avv/pep)
+configure_file(avv/mood.in         avv/mood)
+configure_file(avv/pep_mood.in     avv/pep_mood)
+configure_file(avv/tune.in         avv/tune)
+configure_file(avv/pep_tune.in     avv/pep_tune)
+configure_file(avv/geoloc.in       avv/geoloc)
+configure_file(avv/pep_geoloc.in   avv/pep_geoloc)
+configure_file(avv/activity.in     avv/activity)
+configure_file(avv/pep_activity.in avv/pep_activity)
 
 ## Installation
-install(TARGETS pep DESTINATION lib/mcabber) 
+install(TARGETS pep mood pep_mood tune pep_tune geoloc pep_geoloc activity pep_activity DESTINATION lib/mcabber) 
 install(FILES pep.rc COPYING TODO README DESTINATION share/doc/${CPACK_PACKAGE_NAME})
-install(FILES pep.h DESTINATION include/mcabber)
-install(FILES ${PROJECT_BINARY_DIR}/pep.avv DESTINATION share/mcabber/avv/modules RENAME pep)
+install(DIRECTORY doc/ DESTINATION share/doc/${CPACK_PACKAGE_NAME})
+install(FILES pep.h mood.h tune.h geoloc.h activity.h DESTINATION include/mcabber)
+install(DIRECTORY help DESTINATION share/mcabber)
+install(DIRECTORY ${PROJECT_BINARY_DIR}/avv/ DESTINATION share/mcabber/avv/modules)
 
 ## The End ## vim: se ts=4: ##
--- a/README	Wed Jun 29 19:25:16 2011 +0300
+++ b/README	Sun May 20 22:15:51 2012 +0300
@@ -1,11 +1,41 @@
 
-This is a pep module for mcabber. It just provides common message
-listener for other modules like tune, avatar etc.
+# Personal Eventing Protocol modules
+
+This is a set of modules, that provide notfication about other's events
+and allow you to publish yours.
+
+Pep module provides common listener for incoming PEP events.
+
+Tune module provides '/tune' command to publish music, you are listening
+to at the moment. However, it does not by itself does that. You'll need
+some other module, that will provide music information, like 'mpd' one.
+Alternatively, you can set up some scripts to publish information with
+'/tune' command via fifo.
+
+Mood module provides '/mood' command to publish your current mood.
+Currently it does not check for validity of its input, so, it's strongly
+suggested to use provided completion for first argument.
 
-This module have no options, and generally should be loaded
-automatically by other modules.
+Geoloc module provides '/geoloc' command to publish your current location.
+Currently, there are no known use cases of this module, but I hope, that
+someday there will be a module, that will retrieve geolocation information
+from devices and use this module to publish it.
+
+Activity module provides '/activity' command to publish your current activity.
+Currently it does not check for validity of its input either, so, please
+use completion for first two arguments.
 
-INSTALLATION
+Note: In order for you to actually publish something, your server must
+support PEP, and that is still not that widely available. Still, even
+if your server does not, you can load modules to be notified about your
+buddies events.
+
+All of the modules also provide C headers for other C modules to use
+to publish data. Note, however, that I plan to switch to events interface
+for that - it provides multiplexing ability, and in general is more
+flexible.
+
+# Installation
 
 To install it, you need:
 cmake
@@ -30,16 +60,15 @@
 Users of other distributions can select appropriate package
 generator, using cache editor.
 
-LICENSE
+# License
 
 This code underlies terms of GNU GPL v2 or later. You can find it in file COPYING
 from this distribution or on a GNU web-site <http://www.gnu.org/licenses/>.
 
-CONTACTS
+# Contacts
 
 I will be happy to get feedback, patches, suggestions, etc.
 You can send me email or contact via jabber <isbear@unixzone.org.ua>.
 
   -- Myhailo Danylenko <isbear@ukrpost.net>
 
-
--- a/TODO	Wed Jun 29 19:25:16 2011 +0300
+++ b/TODO	Sun May 20 22:15:51 2012 +0300
@@ -1,5 +1,17 @@
 
-do we need to export functions in some special way?
-startup/shutdown hook to inform other modules, that they should free/register handlers?
-due to server bugs, some offline buddies are still notified on publication, causing error reply, that pep accepts as event. check message type/presence of error tag.
+* due to server bugs, some offline buddies are still notified on publication,
+  causing error reply, that pep accepts as event. check message type/presence of error tag.
+* do something about information duplication in CMakeLists
+* check input/output for validity, where applicable
+* extract common code into one place
+* either put request methods into ui modules (compatibility measure) or rename them into pep_*
+* add avatar
+  * pep_avatar, avatar_cache, avatar, aavatar (separate)
+    *       hook_avatar_metadata_in                               @ pep_avatar  
+          | hook_avatar_need_data_in (or symbol-resolved request) @ avatar_cache  
+          V hook_avatar_data_in                                   @ pep_avatar  
+            hook_avatar_in                                        @ avatar_cache
+    * (!!!) how to handle outgoing event? what and where should do what?
+  * handle url avatars (curl)
+  * report absence of avatar
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/activity.c	Sun May 20 22:15:51 2012 +0300
@@ -0,0 +1,330 @@
+/*
+ * activity.c           -- Pep activity events
+ *
+ * Copyright (C) 2009-2012 Myhailo Danylenko <isbear@ukrpost.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <glib.h>
+#include <string.h>
+
+#include <mcabber/utils.h>
+#include <mcabber/screen.h>
+#include <mcabber/logprint.h>
+#include <mcabber/hbuf.h>         // HBUF_PREFIX_*
+#include <mcabber/roster.h>
+#include <mcabber/hooks.h>
+#include <mcabber/commands.h>
+#include <mcabber/compl.h>
+#include <mcabber/modules.h>
+
+#include "activity.h"
+
+#include "config.h"
+
+//
+//  module description
+//
+
+void activity_init   (void);
+void activity_uninit (void);
+
+#define DESCRIPTION ( "PEP activity support\nProvides command /activity" )
+static const gchar *deps[] = { "pep_activity", NULL };
+
+static module_info_t info_activity_dev = {
+	.branch       = "dev",
+	.api          = 20,
+	.version      = PROJECT_VERSION,
+	.description  = DESCRIPTION,
+	.requires     = deps,
+	.init         = activity_init,
+	.uninit       = activity_uninit,
+	.next         = NULL,
+};
+
+static module_info_t info_activity_0_10_1 = {
+	.branch       = "0.10.1",
+	.api          = 1,
+	.version      = PROJECT_VERSION,
+	.description  = DESCRIPTION,
+	.requires     = deps,
+	.init         = activity_init,
+	.uninit       = activity_uninit,
+	.next         = &info_activity_dev,
+};
+
+module_info_t info_activity = {
+	.branch       = "0.10.0",
+	.api          = 1,
+	.version      = PROJECT_VERSION,
+	.description  = DESCRIPTION,
+	.requires     = deps,
+	.init         = activity_init,
+	.uninit       = activity_uninit,
+	.next         = &info_activity_0_10_1,
+};
+
+//
+//  globals
+//
+
+#ifdef MCABBER_API_HAVE_CMD_ID
+static gpointer activity_cmid     = NULL;
+static gboolean activity_set_safe = FALSE;
+#endif
+
+static guint activity_cid1           = 0;
+static guint activity_cid2           = 0;
+static guint activity_hid_activityin = 0;
+
+//
+//  code
+//
+
+static void do_activity (char *arg)
+{
+	if (!*arg) { // request
+
+		GError *error = NULL;
+
+		activity_request ( CURRENT_JID, &error );
+		if ( error ) {
+			scr_log_print ( LPRINT_NORMAL, "Error sending request: %s.", error -> message );
+			g_error_free ( error );
+		} else
+			scr_log_print ( LPRINT_NORMAL, "Request sent." );
+
+	} else { // publish
+
+		hk_arg_t hookargs[] = {
+			{ "major", NULL },
+			{ "minor", NULL },
+			{ "text",  NULL },
+			{ NULL,    NULL },
+		};
+
+		if ( arg[0] != '-' || arg[1] != '\0' ) {
+			gchar **args = split_arg ( arg, 3, 1 );
+			
+			hookargs[0].value = to_utf8 ( args[0] );
+
+			if ( args[1] ) {
+				if ( args[1][0] != '-' || args[1][1] != '\0' )
+					hookargs[1].value = to_utf8 ( args[1] );
+
+				if ( args[2] )
+					hookargs[2].value = to_utf8 ( args[2] );
+			}
+
+			free_arg_lst ( args );
+		}
+
+		hk_run_handlers ( HOOK_ACTIVITY_OUT, hookargs );
+
+		g_free ( (gchar *) hookargs[0].value );
+		g_free ( (gchar *) hookargs[1].value );
+		g_free ( (gchar *) hookargs[2].value );
+	}
+}
+
+static guint activity_haih ( const gchar *hid, hk_arg_t *args, gpointer userdata )
+{
+	const gchar *from  = NULL;
+	const gchar *major = NULL;
+	const gchar *minor = NULL;
+	const gchar *text  = NULL;
+
+	{
+		hk_arg_t *arg;
+		for ( arg = args; arg -> name; arg ++ ) {
+			const gchar *value = arg -> value;
+			if ( value ) {
+				const gchar *name = arg -> name;
+				if ( ! strcmp ( name, "from" ) )
+					from = value;
+				else if ( ! strcmp ( name, "major" ) )
+					major = value;
+				else if ( ! strcmp ( name, "minor" ) )
+					minor = value;
+				else if ( ! strcmp ( name, "text" ) )
+					text = value;
+			}
+		}
+	}
+
+	{ // print to buddy's buffer
+		gchar *jid  = jidtodisp (from);
+		gchar *mesg = NULL;
+
+		// this can be implemented easier with gstring...
+		if (major && text) {
+			if (minor)
+				mesg = g_strdup_printf ("Activity: %s (%s) - %s.", major, minor, text);
+			else
+				mesg = g_strdup_printf ("Activity: %s - %s.", major, text);
+		} else if (major) {
+			if (minor)
+				mesg = g_strdup_printf ("Activity: %s (%s).", major, minor);
+			else
+				mesg = g_strdup_printf ("Activity: %s.", major);
+		} else if (text)
+			mesg = g_strdup_printf ("Activity: %s.", text);
+
+		scr_write_incoming_message (jid, mesg ? mesg : "No specific activity.", 0, HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0); // NO conversion from utf-8
+
+		g_free (mesg);
+		g_free (jid);
+	}
+
+	return HOOK_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+}
+
+static const gchar *defined_general_activities[] = {
+	"doing_chores",
+	"drinking",
+	"eating",
+	"exercising",
+	"grooming",
+	"having_appointment",
+	"inactive",
+	"relaxing",
+	"talking",
+	"traveling",
+	"working",
+	NULL,
+};
+
+static const gchar *defined_minor_activities[] = {
+	"buying_groceries",
+	"cleaning",
+	"cooking",
+	"doing_maintenance",
+	"doing_the_dishes",
+	"doing_the_laundry",
+	"gardening",
+	"running_an_errand",
+	"walking_the_dog",
+	"having_a_beer",
+	"having_coffee",
+	"having_tea",
+	"having_a_snack",
+	"having_breakfast",
+	"having_dinner",
+	"having_lunch",
+	"cycling",
+	"dancing",
+	"hiking",
+	"jogging",
+	"playing_sports",
+	"running",
+	"skiing",
+	"swimming",
+	"working_out",
+	"at_the_spa",
+	"brushing_teeth",
+	"getting_a_haircut",
+	"shaving",
+	"taking_a_bath",
+	"taking_a_shower",
+	"day_off",
+	"hanging_out",
+	"hiding",
+	"on_vacation",
+	"praying",
+	"scheduled_holiday",
+	"sleeping",
+	"thinking",
+	"fishing",
+	"gaming",
+	"going_out",
+	"partying",
+	"reading",
+	"rehearsing",
+	"shopping",
+	"smoking",
+	"socializing",
+	"sunbathing",
+	"watching_tv",
+	"watching_a_movie",
+	"in_real_life",
+	"on_the_phone",
+	"on_video_phone",
+	"commuting",
+	"cycling",
+	"driving",
+	"in_a_car",
+	"on_a_bus",
+	"on_a_plane",
+	"on_a_train",
+	"on_a_trip",
+	"walking",
+	"coding",
+	"in_a_meeting",
+	"studying",
+	"writing",
+	NULL,
+};
+
+void activity_init (void)
+{
+	activity_cid1 = compl_new_category ();
+	if (activity_cid1) {
+		const gchar **activity;
+
+		for (activity = defined_general_activities; *activity; ++activity)
+			compl_add_category_word (activity_cid1, *activity);
+	}
+
+	activity_cid2 = compl_new_category ();
+	if (activity_cid2) {
+		const gchar **activity;
+
+		for (activity = defined_minor_activities; *activity; ++activity)
+			compl_add_category_word (activity_cid2, *activity);
+	}
+
+#ifndef MCABBER_API_HAVE_CMD_ID
+	cmd_add ("activity", "", activity_cid1, activity_cid2, do_activity, NULL);
+#else
+	activity_cmid = cmd_add ("activity", "", activity_cid1, activity_cid2, do_activity, NULL);
+	activity_set_safe = cmd_set_safe ("activity", TRUE);
+#endif
+
+	activity_hid_activityin = hk_add_handler (activity_haih, HOOK_ACTIVITY_IN, G_PRIORITY_DEFAULT, NULL);
+}
+
+void activity_uninit (void)
+{
+	hk_del_handler (HOOK_ACTIVITY_IN, activity_hid_activityin);
+
+#ifndef MCABBER_API_HAVE_CMD_ID
+	cmd_del ("activity");
+#else
+	if (activity_cmid)
+		cmd_del (activity_cmid);
+	if (activity_set_safe)
+		cmd_set_safe ("activity", FALSE);
+#endif
+
+	if (activity_cid1)
+		compl_del_category (activity_cid1);
+	if (activity_cid2)
+		compl_del_category (activity_cid2);
+}
+
+/* vim: se ts=4 sw=4: */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/activity.h	Sun May 20 22:15:51 2012 +0300
@@ -0,0 +1,40 @@
+
+#ifndef MCABBER_ACTIVITY_H
+#define MCABBER_ACTIVITY_H
+
+/*
+ * activity.h           -- Pep activity events
+ *
+ * Copyright (C) 2012      Myhailo Danylenko <isbear@ukrpost.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <glib.h>
+
+#define NS_ACTIVITY        ( "http:/" "/jabber.org/protocol/activity"        )
+#define NS_ACTIVITY_NOTIFY ( "http:/" "/jabber.org/protocol/activity+notify" )
+
+#define HOOK_ACTIVITY_IN  ( "activity-in"  )
+#define HOOK_ACTIVITY_OUT ( "activity-out" )
+
+#define ACTIVITY_ERROR_NOTCONNECTED ( 0x01 )
+
+void activity_publish ( const gchar *major, const gchar *minor, const gchar *text );
+gboolean activity_request ( const gchar *to, GError **error );
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/avv/activity.in	Sun May 20 22:15:51 2012 +0300
@@ -0,0 +1,13 @@
+
+Name:        activity
+Method:      glib
+Version:     ${PROJECT_VERSION}
+Description: ${CPACK_PACKAGE_DESCRIPTION_SUMMARY}
+# commands depend on building environment - 4, 3 or 2+1
+Requires:    commands:4 + ( compl:3 | compl:2 ) + (hooks:5 | hooks:4 | hooks:3 | hooks:2 ) +
+             pep_activity:1 + logprint:3 + ( hbuf:3 | hbuf:2 | hbuf:1 ) +
+	     ( screen:10 | screen:9 | screen:8 | screen:7 | screen:6 | screen:5 | screen:4 ) +
+	     ( roster:4 | roster:3 | roster:2 | roster:1 ) + utils:2
+Init:        activity_init
+Uninit:      activity_uninit
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/avv/geoloc.in	Sun May 20 22:15:51 2012 +0300
@@ -0,0 +1,14 @@
+
+Name:        geoloc
+Method:      glib
+Version:     ${PROJECT_VERSION}
+Description: ${CPACK_PACKAGE_DESCRIPTION_SUMMARY}
+# commands depend on compilaton environment
+Requires:    ( roster:4 | roster:3 | roster:2 | roster:1 ) + commands:4 + ( compl:3 | compl:2 ) +
+             ( hooks:5 | hooks:4 | hooks:3 | hooks:2 | hooks:1 ) + utils:2 +
+	     pep_geoloc:1 + logprint:3 +
+	     ( screen:10 | screen:9 | screen:8 | screen:7 | screen:6 | screen:5 | screen:4 ) +
+	     ( hbuf:3 | hbuf:2 | hbuf:1 )
+Init:        geoloc_init
+Uninit:      geoloc_uninit
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/avv/mood.in	Sun May 20 22:15:51 2012 +0300
@@ -0,0 +1,15 @@
+
+Name:        mood
+Method:      glib
+Version:     ${PROJECT_VERSION}
+Description: ${CPACK_PACKAGE_DESCRIPTION_SUMMARY}
+# commands depend on building environment: 4, 3, 2+1
+Requires:    ( roster:4 | roster:3 | roster:2 | roster:1 ) + commands:4 +
+             ( compl:3 | compl:2 | compl:1 ) + utils:2 +
+	     ( hooks:5 | hooks:4 | hooks:3 | hooks:2 ) +
+	     logprint:3 + pep_mood:1 +
+	     ( screen:10 | screen:9 | screen:8 | screen:7 | screen:6 | screen:5 | screen:4 ) +
+	     ( hbuf:3 | hbuf:2 | hbuf:1 )
+Init:        mood_init
+Uninit:      mood_uninit
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/avv/pep.in	Sun May 20 22:15:51 2012 +0300
@@ -0,0 +1,13 @@
+
+Name:        pep
+Method:      glib
+Version:     ${PROJECT_VERSION}
+Description: ${CPACK_PACKAGE_DESCRIPTION_SUMMARY}
+             Provides common listener infrastructure for various other
+             modules, eg avatar, geoloc, mood, tune.
+Requires:    ( xmpp:4 | xmpp:3 | xmpp:2 | xmpp:1 ) +
+             ( hooks:5 | hooks:4 | hooks:3 | hooks:2 )
+Provides:    pep:1
+Init:        pep_init
+Uninit:      pep_uninit
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/avv/pep_activity.in	Sun May 20 22:15:51 2012 +0300
@@ -0,0 +1,12 @@
+
+Name:        pep_activity
+Method:      glib
+Version:     ${PROJECT_VERSION}
+Description: ${CPACK_PACKAGE_DESCRIPTION_SUMMARY}
+Requires:    ( hooks:5 | hooks:4 | hooks:3 | hooks:2 ) +
+             ( xmpp:4 | xmpp:3 | xmpp:2 | xmpp:1 ) + pep:1 + logprint:3 +
+	     utils:2 + ( xmpp_helper:2 | xmpp_helper:1 )
+Provides:    pep_activity:1 + activity:1
+Init:        pep_activity_init
+Uninit:      pep_activity_uninit
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/avv/pep_geoloc.in	Sun May 20 22:15:51 2012 +0300
@@ -0,0 +1,13 @@
+
+Name:        pep_geoloc
+Method:      glib
+Version:     ${PROJECT_VERSION}
+Description: ${CPACK_PACKAGE_DESCRIPTION_SUMMARY}
+Requires:    ( hooks:5 | hooks:4 | hooks:3 | hooks:2 | hooks:1 ) + utils:2 +
+	     ( xmpp:4 | xmpp:3 | xmpp:2 | xmpp:1 ) + pep:1 + logprint:3 +
+	     settings:1 + guards:2 +
+	     ( xmpp_helper:2 | xmpp_helper:1 )
+Provides:    pep_geoloc:1 + geoloc:1
+Init:        pep_geoloc_init
+Uninit:      pep_geoloc_uninit
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/avv/pep_mood.in	Sun May 20 22:15:51 2012 +0300
@@ -0,0 +1,13 @@
+
+Name:        pep_mood
+Method:      glib
+Version:     ${PROJECT_VERSION}
+Description: ${CPACK_PACKAGE_DESCRIPTION_SUMMARY}
+# commands depend on building environment: 4, 3, 2+1
+Requires:    utils:2 + ( hooks:5 | hooks:4 | hooks:3 | hooks:2 ) +
+	     ( xmpp:4 | xmpp:3 | xmpp:2 | xmpp:1 ) + pep:1 + logprint:3 +
+	     ( xmpp_helper:2 | xmpp_helper:1 )
+Provides:    pep_mood:1 + mood:1
+Init:        pep_mood_init
+Uninit:      pep_mood_uninit
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/avv/pep_tune.in	Sun May 20 22:15:51 2012 +0300
@@ -0,0 +1,13 @@
+
+Name:        pep_tune
+Method:      glib
+Version:     ${PROJECT_VERSION}
+Description: ${CPACK_PACKAGE_DESCRIPTION_SUMMARY}
+Requires:    ( hooks:5 | hooks:4 | hooks:3 | hooks:2 ) +
+             ( xmpp:4 | xmpp:3 | xmpp:2 | xmpp:1 ) + pep:1 + logprint:3 +
+	     settings:1 + guards:2 + utils:2 +
+	     ( xmpp_helper:2 | xmpp_helper:1 )
+Provides:    pep_tune:1 + tune:1
+Init:        pep_tune_init
+Uninit:      pep_tune_uninit
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/avv/tune.in	Sun May 20 22:15:51 2012 +0300
@@ -0,0 +1,12 @@
+
+Name:        tune
+Method:      glib
+Version:     ${PROJECT_VERSION}
+Description: ${CPACK_PACKAGE_DESCRIPTION_SUMMARY}
+Requires:    commands:4 + ( compl:3 | compl:2 ) + ( hooks:5 | hooks:4 | hooks:3 | hooks:2 ) + logprint:3 +
+	     ( screen:10 | screen:9 | screen:8 | screen:7 | screen:6 | screen:5 | screen:4 ) +
+	     ( roster:4 | roster:3 | roster:2 | roster:1 ) + utils:2 +
+	     ( hbuf:3 | hbuf:2 | hbuf:1 ) + pep_tune:1
+Init:        tune_init
+Uninit:      tune_uninit
+
--- a/config.h.in	Wed Jun 29 19:25:16 2011 +0300
+++ b/config.h.in	Sun May 20 22:15:51 2012 +0300
@@ -2,6 +2,8 @@
 #ifndef LOCAL_CONFIG_H
 #define LOCAL_CONFIG_H
 
+#cmakedefine HAVE_LM_CONNECTION_UNREGISTER_REPLY_HANDLER
+
 #define PROJECT_VERSION ( "${PROJECT_VERSION}" )
 
 #endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/activities.txt	Sun May 20 22:15:51 2012 +0300
@@ -0,0 +1,78 @@
+     * doing_chores
+          + buying_groceries
+          + cleaning
+          + cooking
+          + doing_maintenance
+          + doing_the_dishes
+          + doing_the_laundry
+          + gardening
+          + running_an_errand
+          + walking_the_dog
+     * drinking
+          + having_a_beer
+          + having_coffee
+          + having_tea
+     * eating
+          + having_a_snack
+          + having_breakfast
+          + having_dinner
+          + having_lunch
+     * exercising
+          + cycling
+          + dancing
+          + hiking
+          + jogging
+          + playing_sports
+          + running
+          + skiing
+          + swimming
+          + working_out
+     * grooming
+          + at_the_spa
+          + brushing_teeth
+          + getting_a_haircut
+          + shaving
+          + taking_a_bath
+          + taking_a_shower
+     * having_appointment
+     * inactive
+          + day_off
+          + hanging_out
+          + hiding
+          + on_vacation
+          + praying
+          + scheduled_holiday
+          + sleeping
+          + thinking
+     * relaxing
+          + fishing
+          + gaming
+          + going_out
+          + partying
+          + reading
+          + rehearsing
+          + shopping
+          + smoking
+          + socializing
+          + sunbathing
+          + watching_tv
+          + watching_a_movie
+     * talking
+          + in_real_life
+          + on_the_phone
+          + on_video_phone
+     * traveling
+          + commuting
+          + cycling
+          + driving
+          + in_a_car
+          + on_a_bus
+          + on_a_plane
+          + on_a_train
+          + on_a_trip
+          + walking
+     * working
+          + coding
+          + in_a_meeting
+          + studying
+          + writing
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/api.mdwn	Sun May 20 22:15:51 2012 +0300
@@ -0,0 +1,119 @@
+
+# Events interface
+
+Events are currently designed for useage with pep notifications, but
+potentially could be used with any notification system, eg a module, that adds
+currently playing song to your status.
+
+Every module pair (eg pep_tune and tune) use a pair of hooks. One for incoming
+notifications ('*-in') and one for the cases, when server needs to be notified
+about user state change ('*-out'). Note, that '*-out' causes server
+notification, not vice versa. In general, these hooks have the same set of fields,
+except that incoming events also have 'from' field, that is mandatory.
+
+For more clear description of fields and what can be put in there, refer to
+corresponding XEPs.
+
+* [XEP-0118](http://xmpp.org/extensions/xep-0118.html) - tune
+* [XEP-0107](http://xmpp.org/extensions/xep-0107.html) - mood
+* [XEP-0108](http://xmpp.org/extensions/xep-0108.html) - activity
+* [XEP-0080](http://xmpp.org/extensions/xep-0080.html) - geoloc
+
+## tune-*
+* argtist
+* length
+* rating
+* source
+* title
+* track
+* uri
+
+## mood-*
+* mood
+* text
+
+## activity-*
+* major
+* minor
+* text
+
+## geoloc-*
+* accuracy
+* alt
+* area
+* bearing
+* building
+* country
+* datum
+* description
+* error
+* floor
+* lat
+* locality
+* lon
+* postalcode
+* region
+* room
+* speed
+* street
+* text
+* timestamp
+* uri
+
+# C interface
+
+## general conventions
+
+### NS_*
+XMLNS namespace constants.
+
+### HOOK_*
+Hook name constants. Usually are the same as actual hook name, but
+uppercased, with 'HOOK_' added and with underscores instead of dashes.
+
+### *_request ( to, error )
+Request corresponding data from the jid, specified in 'to'.
+'error' may be used for error control. If no error occured,
+and remote side have replied with notification stanza, call
+to one of this functions will eventually result in corresponding
+event '*-in'. Currently there's no way to detect an
+error on the remote side (eg, if it does not support PEP).
+
+### *_publish ( ... )
+Publish supplied data as corresponding user's update. Note,
+that this function bypasses event interface, thus, it can
+be used to publish *only* via PEP. In general, it is
+suggested to use event (hooks) interface, as it allows
+multiplexing and non-breaking extensibility.
+
+## pep
+
+### (*pep_xmlns_handler_t) ( from, node, n, id, userdata )
+Type of function to supply as a handler for pep xmlns namespace.
+'from' is a jid.
+'node' is pep node.
+'n' is loudmouth message node object, containing xmlns-specific data.
+'userdata' is pointer to data, passed at registration time.
+
+### pep_register_xmlns_handler ( xmlns, handler, userdata, notify )
+Specify a handler for xmlns namespace.
+'userdata' will be directly specified.
+'notify' is a function, that may be called for userdata destruction.
+
+### pep_unregister_xmlns_handler ( xmlns )
+Remove handler from given xmlns namespace.
+
+## tune
+'tune_publish' takes list of key-value pairs.
+
+## mood
+'mood_publish' takes two string arguments - mood and mood message.
+
+## activity
+'activity_publish' takes three string arguments - major activity, minor, and
+activity message.
+
+## geoloc
+'geoloc_publish' takes list of key-value pairs.
+
+<!-- -->
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/moods.txt	Sun May 20 22:15:51 2012 +0300
@@ -0,0 +1,84 @@
+afraid
+amazed
+amorous
+angry
+annoyed
+anxious
+aroused
+ashamed
+bored
+brave
+calm
+cautious
+cold
+confident
+confused
+contemplative
+contented
+cranky
+crazy
+creative
+curious
+dejected
+depressed
+disappointed
+disgusted
+dismayed
+distracted
+embarrassed
+envious
+excited
+flirtatious
+frustrated
+grateful
+grieving
+grumpy
+guilty
+happy
+hopeful
+hot
+humbled
+humiliated
+hungry
+hurt
+impressed
+in_awe
+in_love
+indignant
+interested
+intoxicated
+invincible
+jealous
+lonely
+lost
+lucky
+mean
+moody
+nervous
+neutral
+offended
+outraged
+playful
+proud
+relaxed
+relieved
+remorseful
+restless
+sad
+sarcastic
+satisfied
+serious
+shocked
+shy
+sick
+sleepy
+spontaneous
+stressed
+strong
+surprised
+thankful
+thirsty
+tired
+undefined
+weak
+worried
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/geoloc.c	Sun May 20 22:15:51 2012 +0300
@@ -0,0 +1,323 @@
+/*
+ * geoloc.c             -- Pep geographical location events
+ *
+ * Copyright (C) 2009-2012 Myhailo Danylenko <isbear@ukrpost.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <glib.h>
+#include <string.h>
+
+#include <mcabber/commands.h>
+#include <mcabber/compl.h>
+#include <mcabber/utils.h>
+#include <mcabber/screen.h>
+#include <mcabber/logprint.h>
+#include <mcabber/hbuf.h>        // HBUF_PREFIX_*
+#include <mcabber/roster.h>
+#include <mcabber/hooks.h>
+#include <mcabber/modules.h>
+
+#include "geoloc.h"
+
+#include "config.h"
+
+//
+//  module description
+//
+
+void geoloc_init   (void);
+void geoloc_uninit (void);
+
+#define DESCRIPTION ( \
+	"PEP geoloc event handling\n" \
+	"Provides command /geoloc" )
+
+static const gchar *deps[] = { "pep_geoloc", NULL };
+
+static module_info_t info_geoloc_dev = {
+	.branch      = "dev",
+	.api         = 20,
+	.version     = PROJECT_VERSION,
+	.description = DESCRIPTION,
+	.requires    = deps,
+	.init        = geoloc_init,
+	.uninit      = geoloc_uninit,
+	.next        = NULL,
+};
+
+module_info_t info_geoloc = {
+	.branch      = "0.10.1",
+	.api         = 1,
+	.version     = PROJECT_VERSION,
+	.description = DESCRIPTION,
+	.requires    = deps,
+	.init        = geoloc_init,
+	.uninit      = geoloc_uninit,
+	.next        = &info_geoloc_dev,
+};
+
+//
+//  globals
+//
+
+#ifdef MCABBER_API_HAVE_CMD_ID
+static gpointer geoloc_cmid     = NULL;
+static gboolean geoloc_set_safe = FALSE;
+#endif
+
+static guint geoloc_cid          = 0;
+static guint geoloc_hid_geolocin = 0;
+
+//
+//  code
+//
+
+static void do_geoloc (char *arg)
+{
+	geoloc_pair_t tags[] = {
+		{ "accuracy",    NULL },
+		{ "alt",         NULL },
+		{ "area",        NULL },
+		{ "bearing",     NULL },
+		{ "building",    NULL },
+		{ "country",     NULL },
+		{ "countrycode", NULL },
+		{ "datum",       NULL },
+		{ "description", NULL },
+		{ "error",       NULL },
+		{ "floor",       NULL },
+		{ "lat",         NULL },
+		{ "locality",    NULL },
+		{ "lon",         NULL },
+		{ "postalcode",  NULL },
+		{ "region",      NULL },
+		{ "room",        NULL },
+		{ "speed",       NULL },
+		{ "street",      NULL },
+		{ "text",        NULL },
+		{ "timestamp",   NULL },
+		{ "uri",         NULL },
+		{ NULL,          NULL },
+	};
+
+	if (!*arg) { // request
+
+		GError *error = NULL;
+
+		geoloc_request ( CURRENT_JID, &error );
+		if ( error ) {
+			scr_log_print ( LPRINT_NORMAL, "Error sending request: %s.", error -> message );
+			g_error_free ( error );
+		} else
+			scr_log_print ( LPRINT_NORMAL, "Request sent." );
+
+		return;
+
+	} else if (arg[0] != '-' || arg[1] != '\0') { // publish
+
+		char          *p;
+		char          *argstart  = NULL;
+		char          *argstop   = NULL;
+		char          *tagstart  = NULL;
+		char          *tagstop   = NULL;
+		char          *wordstart = arg;
+		gboolean       proceed   = TRUE;
+
+// pt = p, ws = wordstart, ts = tagstart, tt = tagstop, as = argstart, at = arstop
+// tag=value value tag=value
+// w  p
+// s  t
+// t  ta
+// s  t*
+// tag=value value tag=value
+// t  ta    p
+// s  t*    t
+// t  ta    aw
+// s  ts    ts
+// tag=value value tag=value
+// t  ta    aw    p
+// s  ts    ts    t
+// t  ta          aw
+// s  ts          ts
+// tag=value value tag=value
+// t  ta          aw  p
+// s  ts          ts  t
+//                 t  ta
+//                 s  t*
+// tag=value value tag=value
+//                 t  ta    p
+//                 s  t*    t
+//                 t  ta    a
+//                 s  ts    t
+
+		for (p = arg; proceed; ++p) {
+			switch (*p) {
+			case '=':
+				if (tagstart && tagstop - tagstart) {
+					// process previous args
+					geoloc_pair_t *tag;
+
+					for (tag = tags; tag->name; ++tag) {
+						if (!strncmp (tag->name, tagstart, tagstop - tagstart)) {
+							g_free ( (gchar *) tag -> value );
+							if (argstop - argstart) {
+								*argstop = '\0';
+								tag->value = to_utf8 (argstart);
+							}
+							break;
+						}
+					}
+				}
+
+				tagstart = wordstart;
+				tagstop = p;
+				argstop = p+1;
+				argstart = p+1;
+				break;
+
+			case '\0':
+				argstop = p;
+
+				if (tagstop - tagstart) {
+					// process previous args
+					geoloc_pair_t *tag;
+
+					for (tag = tags; tag->name; ++tag) {
+						if (!strncmp (tag->name, tagstart, tagstop - tagstart)) {
+							g_free ( (gchar *) tag -> value );
+							if (argstop - argstart)
+								tag->value = to_utf8 (argstart);
+							break;
+						}
+					}
+				}
+
+				proceed = FALSE;
+				break;
+
+			case ' ':
+				argstop = p;
+				wordstart = p+1;
+				break;
+
+			default:
+				break;
+			}
+		}
+	}
+
+	hk_run_handlers ( HOOK_GEOLOC_OUT, (hk_arg_t *) tags );
+
+	{
+		geoloc_pair_t *tag;
+
+		for ( tag = tags; tag -> name; ++ tag )
+			g_free ( (gchar *) tag -> value );
+	}
+}
+
+static guint geoloc_hgih ( const gchar *hid, hk_arg_t *args, gpointer userdata )
+{
+	GString     *mesg = g_string_new (NULL);
+	const gchar *from = NULL;
+
+	{
+		hk_arg_t *arg;
+		for ( arg = args; arg -> name; ++ arg )
+			if ( arg -> value ) {
+				if ( ! strcmp ( arg -> name, "from" ) )
+					from = arg -> value;
+				else
+					g_string_append_printf ( mesg, "\n - %s: %s", arg -> name, arg -> value);
+			}
+	}
+
+	if (mesg->len)
+		g_string_prepend (mesg, "Now located at:");
+	else
+		g_string_overwrite (mesg, 0, "No location information.");
+
+	{ // print to buddy's buffer
+		gchar *jid = jidtodisp (from);
+
+		scr_write_incoming_message (jid, mesg->str, 0, HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0); // NO conversion from utf-8
+
+		g_free (jid);
+	}
+
+	g_string_free (mesg, TRUE);
+
+	return HOOK_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+}
+
+void geoloc_init ( void )
+{
+	geoloc_cid = compl_new_category ();
+	if (geoloc_cid) {
+		compl_add_category_word (geoloc_cid, "accuracy=");
+		compl_add_category_word (geoloc_cid, "alt=");
+		compl_add_category_word (geoloc_cid, "area=");
+		compl_add_category_word (geoloc_cid, "bearing=");
+		compl_add_category_word (geoloc_cid, "building=");
+		compl_add_category_word (geoloc_cid, "country=");
+		compl_add_category_word (geoloc_cid, "countrycode=");
+		compl_add_category_word (geoloc_cid, "datum=");
+		compl_add_category_word (geoloc_cid, "description=");
+		compl_add_category_word (geoloc_cid, "error=");
+		compl_add_category_word (geoloc_cid, "floor=");
+		compl_add_category_word (geoloc_cid, "lat=");
+		compl_add_category_word (geoloc_cid, "locality=");
+		compl_add_category_word (geoloc_cid, "lon=");
+		compl_add_category_word (geoloc_cid, "postalcode=");
+		compl_add_category_word (geoloc_cid, "region=");
+		compl_add_category_word (geoloc_cid, "room=");
+		compl_add_category_word (geoloc_cid, "speed=");
+		compl_add_category_word (geoloc_cid, "street=");
+		compl_add_category_word (geoloc_cid, "text=");
+		compl_add_category_word (geoloc_cid, "timestamp=");
+		compl_add_category_word (geoloc_cid, "uri=");
+	}
+
+#ifndef MCABBER_API_HAVE_CMD_ID
+	cmd_add ("geoloc", "", geoloc_cid, geoloc_cid, do_geoloc, NULL);
+#else
+	geoloc_cmid     = cmd_add ("geoloc", "", geoloc_cid, geoloc_cid, do_geoloc, NULL);
+	geoloc_set_safe = cmd_set_safe ("geoloc", TRUE);
+#endif
+
+	geoloc_hid_geolocin = hk_add_handler (geoloc_hgih, HOOK_GEOLOC_IN, G_PRIORITY_DEFAULT, NULL);
+}
+
+void geoloc_uninit ( void )
+{
+	hk_del_handler (HOOK_GEOLOC_IN, geoloc_hid_geolocin);
+
+#ifndef MCABBER_API_HAVE_CMD_ID
+	cmd_del ("geoloc");
+#else
+	if (geoloc_cmid)
+		cmd_del (geoloc_cmid);
+	if (geoloc_set_safe)
+		cmd_set_safe ("geoloc", FALSE);
+#endif
+
+	if (geoloc_cid)
+		compl_del_category (geoloc_cid);
+}
+
+/* vim: se ts=4 sw=4: */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/geoloc.h	Sun May 20 22:15:51 2012 +0300
@@ -0,0 +1,45 @@
+
+#ifndef MCABBER_GEOLOC_H
+#define MCABBER_GEOLOC_H
+
+/*
+ * geoloc.h             -- Pep geographical location events
+ *
+ * Copyright (C) 2009-2012 Myhailo Danylenko <isbear@ukrpost.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <glib.h>
+
+#define NS_GEOLOC        ( "http:/" "/jabber.org/protocol/geoloc"        )
+#define NS_GEOLOC_NOTIFY ( "http:/" "/jabber.org/protocol/geoloc+notify" )
+
+#define HOOK_GEOLOC_IN  ( "geoloc-in"  )
+#define HOOK_GEOLOC_OUT ( "geoloc-out" )
+
+#define GEOLOC_ERROR_NOTCONNECTED ( 0x01 )
+
+typedef struct {
+	const gchar *name;
+	gchar       *value;
+} geoloc_pair_t;
+
+void geoloc_publish ( const geoloc_pair_t *pairs );
+gboolean geoloc_request ( const gchar *to, GError **error );
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/en/hlp_activity.txt	Sun May 20 22:15:51 2012 +0300
@@ -0,0 +1,12 @@
+
+ /ACTIVITY [general|- [minor|- [message]]]
+
+Publishes your activity. For valid activities see completion or standard.
+Note, that it does not validate specified values, so, it is your responsibility to take care about that.
+
+/activity
+ Requests current buddy's activity.
+/activity -
+ Publishes empty information about your activity.
+/activity general-activity [minor-activity|- [message]]
+ Publishes your activity. To specify only general activity and message without minor, specify '-' in place of minor activity.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/en/hlp_geoloc.txt	Sun May 20 22:15:51 2012 +0300
@@ -0,0 +1,13 @@
+
+ /GEOLOC [-]
+ /GEOLOC [key=[value] ...]
+
+Publish your geographical location via pep. For a list of valid keys see completion or standard.
+Note: It will not complain about wrong keys, it will just skip them.
+
+/geoloc
+ Request position from current buddy's server.
+/geoloc -
+ Publish empty position.
+/geoloc [key=[value] ...]
+ Publish some data.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/en/hlp_mood.txt	Sun May 20 22:15:51 2012 +0300
@@ -0,0 +1,12 @@
+
+ /MOOD [-|mood [message]]
+
+Publishes your mood via pep. For valid moods see standard or completion.
+Note, that it does not checks, if specified value is valid, it only provides completion.
+
+/mood
+ Sends request for current buddy's mood.
+/mood -
+ Publishes empty mood (no mood).
+/mood mood [message]
+ Publishes specified mood.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/en/hlp_tune.txt	Sun May 20 22:15:51 2012 +0300
@@ -0,0 +1,5 @@
+
+ /TUNE [artist|length|rating|source|title|track|uri=[value] ...]
+
+Sends tune publish request with supplied information. Value can contain embedded spaces.
+Note: unknown keys will be just silently ignored.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mood.c	Sun May 20 22:15:51 2012 +0300
@@ -0,0 +1,294 @@
+/*
+ * mood.c               -- Pep mood events
+ *
+ * Copyright (C) 2009-2012 Myhailo Danylenko <isbear@ukrpost.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <glib.h>
+#include <string.h>
+
+#include <mcabber/commands.h>
+#include <mcabber/compl.h>
+#include <mcabber/utils.h>
+#include <mcabber/screen.h>
+#include <mcabber/logprint.h>
+#include <mcabber/roster.h>
+#include <mcabber/hbuf.h>        // HBUF_PREFIX_*
+#include <mcabber/hooks.h>
+#include <mcabber/modules.h>
+
+#include "mood.h"
+
+#include "config.h"
+
+//
+//  module description
+//
+
+void mood_init   (void);
+void mood_uninit (void);
+
+#define DESCRIPTION ( "PEP mood events handling\nProvides command /mood" )
+
+static const gchar *deps[] = { "pep_mood", NULL };
+
+static module_info_t info_mood_dev = {
+	.branch      = "dev",
+	.api         = 20,
+	.version     = PROJECT_VERSION,
+	.description = DESCRIPTION,
+	.requires    = deps,
+	.init        = mood_init,
+	.uninit      = mood_uninit,
+	.next        = NULL,
+};
+
+module_info_t info_mood = {
+	.branch      = "0.10.1",
+	.api         = 1,
+	.version     = PROJECT_VERSION,
+	.description = DESCRIPTION,
+	.requires    = deps,
+	.init        = mood_init,
+	.uninit      = mood_uninit,
+	.next        = &info_mood_dev,
+};
+
+//
+//  globals
+//
+
+#ifdef MCABBER_API_HAVE_CMD_ID
+static gpointer mood_cmid     = NULL;
+static gboolean mood_set_safe = FALSE;
+#endif
+
+static guint mood_cid        = 0;
+static guint mood_hid_moodin = 0;
+
+//
+//  code
+//
+
+static void do_mood (char *arg)
+{
+	if (!*arg) { // request
+
+		GError *error = NULL;
+		
+		mood_request ( CURRENT_JID, &error );
+		if ( error ) {
+			scr_log_print ( LPRINT_NORMAL, "Error sending request: %s.", error -> message );
+			g_error_free ( error );
+		} else
+			scr_log_print ( LPRINT_NORMAL, "Request sent." );
+
+		return;
+
+	} else { // publish
+		
+		hk_arg_t hookargs[] = {
+			{ "mood", NULL },
+			{ "text", NULL },
+			{ NULL,   NULL },
+		};
+
+		if ( arg[0] != '-' || arg[1] != '\0' ) {
+			gchar **args = split_arg ( arg, 2, 1 );
+
+			hookargs[0].value = to_utf8 ( args[0] );
+
+			if ( args[1] )
+				hookargs[1].value = to_utf8 ( args[1] );
+
+			free_arg_lst ( args );
+		}
+
+		hk_run_handlers ( HOOK_MOOD_OUT, hookargs );
+
+		g_free ( (gchar *) hookargs[0].value );
+		g_free ( (gchar *) hookargs[1].value );
+	}
+}
+
+static guint mood_hmih (const gchar *hid, hk_arg_t *args, gpointer userdata)
+{
+	const gchar *from = NULL;
+	const gchar *mood = NULL;
+	const gchar *text = NULL;
+
+	{
+		hk_arg_t *arg;
+		for ( arg = args; arg -> name; arg ++ ) {
+			const gchar *value = arg -> value;
+			if ( value ) {
+				const gchar *name  = arg -> name;
+				if ( ! strcmp ( name, "from" ) )
+					from = value;
+				else if ( ! strcmp ( name, "mood" ) )
+					mood = value;
+				else if ( ! strcmp ( name, "text" ) )
+					text = value;
+			}
+		}
+	}
+
+	{ // print to buddy's buffer
+		gchar *jid  = jidtodisp (from);
+		gchar *mesg = NULL;
+
+		if (mood && text)
+			mesg = g_strdup_printf ("Mood: %s - %s.", mood, text);
+		else if (mood || text)
+			mesg = g_strdup_printf ("Mood: %s.", mood ? mood : text);
+
+		scr_write_incoming_message (jid, mesg ? mesg : "No specific mood.", 0, HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0); // NO conversion from utf-8
+
+		g_free (mesg);
+		g_free (jid);
+	}
+
+	return HOOK_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+}
+
+static const gchar *defined_moods[] = {
+	"afraid",
+	"amazed",
+	"amorous",
+	"angry",
+	"annoyed",
+	"anxious",
+	"aroused",
+	"ashamed",
+	"bored",
+	"brave",
+	"calm",
+	"cautious",
+	"cold",
+	"confident",
+	"confused",
+	"contemplative",
+	"contented",
+	"cranky",
+	"crazy",
+	"creative",
+	"curious",
+	"dejected",
+	"depressed",
+	"disappointed",
+	"disgusted",
+	"dismayed",
+	"distracted",
+	"embarrassed",
+	"envious",
+	"excited",
+	"flirtatious",
+	"frustrated",
+	"grateful",
+	"grieving",
+	"grumpy",
+	"guilty",
+	"happy",
+	"hopeful",
+	"hot",
+	"humbled",
+	"humiliated",
+	"hungry",
+	"hurt",
+	"impressed",
+	"in_awe",
+	"in_love",
+	"indignant",
+	"interested",
+	"intoxicated",
+	"invincible",
+	"jealous",
+	"lonely",
+	"lost",
+	"lucky",
+	"mean",
+	"moody",
+	"nervous",
+	"neutral",
+	"offended",
+	"outraged",
+	"playful",
+	"proud",
+	"relaxed",
+	"relieved",
+	"remorseful",
+	"restless",
+	"sad",
+	"sarcastic",
+	"satisfied",
+	"serious",
+	"shocked",
+	"shy",
+	"sick",
+	"sleepy",
+	"spontaneous",
+	"stressed",
+	"strong",
+	"surprised",
+	"thankful",
+	"thirsty",
+	"tired",
+	"undefined",
+	"weak",
+	"worried",
+	NULL,
+};
+
+void mood_init (void)
+{
+	mood_cid = compl_new_category ();
+	if (mood_cid) {
+		const gchar **mood;
+
+		for (mood = defined_moods; *mood; ++mood)
+			compl_add_category_word (mood_cid, *mood);
+	}
+
+#ifndef MCABBER_API_HAVE_CMD_ID
+	cmd_add ( "mood", "", mood_cid, 0, do_mood, NULL );
+#else
+	mood_cmid     = cmd_add ( "mood", "", mood_cid, 0, do_mood, NULL );
+	mood_set_safe = cmd_set_safe ( "mood", TRUE );
+#endif
+
+	mood_hid_moodin = hk_add_handler ( mood_hmih, HOOK_MOOD_IN, G_PRIORITY_DEFAULT, NULL );
+}
+
+void mood_uninit (void)
+{
+	hk_del_handler ( HOOK_MOOD_IN, mood_hid_moodin );
+
+#ifndef MCABBER_API_HAVE_CMD_ID
+	cmd_del ( "mood" );
+#else
+	if ( mood_cmid )
+		cmd_del ( mood_cmid );
+	if ( mood_set_safe )
+		cmd_set_safe ( "mood", FALSE );
+#endif
+
+	if ( mood_cid )
+		compl_del_category ( mood_cid );
+}
+
+/* vim: se ts=4 sw=4: */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mood.h	Sun May 20 22:15:51 2012 +0300
@@ -0,0 +1,40 @@
+
+#ifndef MCABBER_MOOD_H
+#define MCABBER_MOOD_H
+
+/*
+ * mood.h               -- Pep mood events
+ *
+ * Copyright (C) 2009-2012 Myhailo Danylenko <isbear@ukrpost.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <glib.h>
+
+#define NS_MOOD        ( "http:/" "/jabber.org/protocol/mood"        )
+#define NS_MOOD_NOTIFY ( "http:/" "/jabber.org/protocol/mood+notify" )
+
+#define HOOK_MOOD_IN  ( "mood-in"  )
+#define HOOK_MOOD_OUT ( "mood-out" )
+
+#define MOOD_ERROR_NOTCONNECTED ( 0x01 )
+
+void mood_publish ( const gchar *mood, const gchar *text );
+gboolean mood_request ( const gchar *to, GError **error );
+
+#endif
+
--- a/pep.avv.in	Wed Jun 29 19:25:16 2011 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,13 +0,0 @@
-
-Name:        ${PROJECT_NAME}
-Method:      glib
-Version:     ${PROJECT_VERSION}
-Description: ${CPACK_PACKAGE_DESCRIPTION_SUMMARY}
-             Provides common listener infrastructure for various other
-             modules, eg avatar, geoloc, mood, tune.
-Requires:    ( xmpp:4 | xmpp:3 | xmpp:2 | xmpp:1 ) +
-             ( hooks:5 | hooks:4 | hooks:3 | hooks:2 )
-Provides:    pep:1
-Init:        pep_init
-Uninit:      pep_uninit
-
--- a/pep.c	Wed Jun 29 19:25:16 2011 +0300
+++ b/pep.c	Sun May 20 22:15:51 2012 +0300
@@ -1,7 +1,7 @@
 /*
  * pep.c                -- Common pep routines
  *
- * Copyrigth (C) 2009      Myhailo Danylenko <isbear@ukrpost.net>
+ * Copyright (C) 2009      Myhailo Danylenko <isbear@ukrpost.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/pep.rc	Wed Jun 29 19:25:16 2011 +0300
+++ b/pep.rc	Sun May 20 22:15:51 2012 +0300
@@ -1,3 +1,37 @@
 
-module load pep
+# Modules, that provide internal infrastructure, and will
+# be loaded automatically by dependencies:
+#module load pep
+#module load pep_mood
+#moudle load pep_tune
+#module load pep_activity
+#module load pep_geoloc
+#module load pep_avatar
+
+# Minimum interval betweet two publishes. Any too frequent
+# publish requests will be discarded. Set to 0 to disable.
+# (pep_tune option)
+set tune_interval = 10
 
+# Minimum interval between two publishes. Any too frequent
+# publish requests will be discarded. Set to 0 to disable.
+# (pep_geoloc option)
+set geoloc_interval = 5
+
+module load mood
+
+module load tune
+
+module load activity
+
+module load geoloc
+
+# Directory, where avatars will be saved. Avatars are saved by
+# their id (sha1sum of png data), and symlinked by jids.
+# I.e. '12345...679.png' contains png data and 'foo@bar.org' is a 
+# symlink to '12345...679.png'
+set avatar_directory = ~/.mcabber/avatars
+
+module load avatar
+
+# the end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pep_activity.c	Sun May 20 22:15:51 2012 +0300
@@ -0,0 +1,347 @@
+/*
+ * pep_activity.c       -- Pep activity events
+ *
+ * Copyright (C) 2009-2012 Myhailo Danylenko <isbear@ukrpost.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <glib.h>
+#include <loudmouth/loudmouth.h>
+#include <string.h>
+
+#include <mcabber/utils.h>
+#include <mcabber/logprint.h>
+#include <mcabber/hooks.h>
+#include <mcabber/modules.h>
+#include <mcabber/xmpp.h>
+#include <mcabber/xmpp_helper.h>
+
+#include "pep.h"
+#include "activity.h"
+
+#include "config.h"
+
+//
+//  module description
+//
+
+void pep_activity_init   (void);
+void pep_activity_uninit (void);
+
+#define DESCRIPTION ( "PEP activity support" )
+static const gchar *deps[] = { "pep", NULL };
+
+static module_info_t info_activity_dev = {
+	.branch       = "dev",
+	.api          = 20,
+	.version      = PROJECT_VERSION,
+	.description  = DESCRIPTION,
+	.requires     = deps,
+	.init         = pep_activity_init,
+	.uninit       = pep_activity_uninit,
+	.next         = NULL,
+};
+
+static module_info_t info_activity_0_10_1 = {
+	.branch       = "0.10.1",
+	.api          = 1,
+	.version      = PROJECT_VERSION,
+	.description  = DESCRIPTION,
+	.requires     = deps,
+	.init         = pep_activity_init,
+	.uninit       = pep_activity_uninit,
+	.next         = &info_activity_dev,
+};
+
+module_info_t info_pep_activity = {
+	.branch       = "0.10.0",
+	.api          = 1,
+	.version      = PROJECT_VERSION,
+	.description  = DESCRIPTION,
+	.requires     = deps,
+	.init         = pep_activity_init,
+	.uninit       = pep_activity_uninit,
+	.next         = &info_activity_0_10_1,
+};
+
+//
+//  globals
+//
+
+static GQuark            activity_gerror_quark    = 0;
+static gboolean          activity_delayed         = FALSE;
+static guint             activity_hid_connect     = 0;
+static guint             activity_hid_disconnect  = 0;
+static guint             activity_hid_activityout = 0;
+static gchar            *activity_major           = NULL;
+static gchar            *activity_minor           = NULL;
+static gchar            *activity_text            = NULL;
+static LmMessageHandler *activity_reply_handler   = NULL;
+
+//
+//  code
+//
+
+static LmHandlerResult activity_publish_reply_handler (LmMessageHandler *handler, LmConnection *connection, LmMessage *message, gpointer userdata)
+{
+	switch (lm_message_get_sub_type (message)) {
+	case LM_MESSAGE_SUB_TYPE_RESULT:
+		break;
+	
+	case LM_MESSAGE_SUB_TYPE_ERROR:
+		
+		{
+			LmMessageNode *node   = lm_message_get_node (message);
+			const gchar   *type;
+			const gchar   *reason;
+
+			node = lm_message_node_get_child (node, "error");
+			type = lm_message_node_get_attribute (node, "type");
+			if (node->children)
+				reason = node->children->name;
+			else
+				reason = "undefined";
+
+			scr_log_print (LPRINT_LOGNORM, "activity: Publish failed: %s - %s", type, reason);
+		}
+
+		break;
+	
+	default:
+		return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+		break;
+	}
+
+	return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+}
+
+void activity_publish (const gchar *major, const gchar *minor, const gchar *text)
+{
+	if (!xmpp_is_online ()) {
+		g_free (activity_major);
+		g_free (activity_minor);
+		g_free (activity_text);
+
+		activity_major   = g_strdup (major);
+		activity_minor   = g_strdup (minor);
+		activity_text    = g_strdup (text);
+		activity_delayed = TRUE;
+
+		return;
+	}
+
+	LmMessage     *request = lm_message_new_with_sub_type (NULL, LM_MESSAGE_TYPE_IQ, LM_MESSAGE_SUB_TYPE_SET);
+	LmMessageNode *node    = lm_message_get_node (request);
+	lm_message_node_set_attribute (node, "from", lm_connection_get_jid (lconnection));
+
+	node = lm_message_node_add_child (node, "pubsub", NULL);
+	lm_message_node_set_attribute (node, "xmlns", NS_PUBSUB);
+
+	node = lm_message_node_add_child (node, "publish", NULL);
+	lm_message_node_set_attribute (node, "node", NS_ACTIVITY);
+
+	node = lm_message_node_add_child (node, "item", NULL);
+
+	node = lm_message_node_add_child (node, "activity", NULL);
+	lm_message_node_set_attribute (node, "xmlns", NS_ACTIVITY);
+
+	if (major) {
+		LmMessageNode *mnode = lm_message_node_add_child (node, major, NULL);
+
+		if (minor)
+			lm_message_node_add_child (mnode, minor, NULL);
+	}
+
+	if (text)
+		lm_message_node_add_child (node, "text", text);
+
+	{ // send
+		GError *error = NULL;
+
+		lm_connection_send_with_reply (lconnection, request, activity_reply_handler, &error);
+
+		if (error) {
+			scr_log_print (LPRINT_DEBUG, "activity: Publish failed: %s.", error -> message);
+			g_error_free (error);
+		}
+	}
+
+	lm_message_unref (request);
+}
+
+gboolean activity_request ( const gchar *to, GError **err )
+{
+	LmMessage     *request;
+	LmMessageNode *node;
+
+	if ( ! xmpp_is_online () ) {
+		g_set_error ( err, activity_gerror_quark, ACTIVITY_ERROR_NOTCONNECTED, "You are not connected" );
+		return FALSE;
+	}
+
+	request = lm_message_new_with_sub_type (to, LM_MESSAGE_TYPE_IQ, LM_MESSAGE_SUB_TYPE_GET);
+	node = lm_message_get_node (request);
+	lm_message_node_set_attribute (node, "from", lm_connection_get_jid (lconnection));
+
+	node = lm_message_node_add_child (node, "pubsub", NULL);
+	lm_message_node_set_attribute (node, "xmlns", NS_PUBSUB);
+
+	node = lm_message_node_add_child (node, "items", NULL);
+	lm_message_node_set_attribute (node, "node", NS_ACTIVITY);
+
+	{ // send, result will be handled by pep
+		GError *error = NULL;
+
+		lm_connection_send (lconnection, request, &error);
+
+		if (error) {
+			g_propagate_error ( err, error );
+			return FALSE;
+		}
+	}
+
+	lm_message_unref (request);
+
+	return TRUE;
+}
+
+static void activity_handler ( const gchar *from, const gchar *enode, LmMessageNode *n, const gchar *id, gpointer ignore )
+{
+	LmMessageNode *node;
+	hk_arg_t args[] = {
+		{ "major", NULL },
+		{ "minor", NULL },
+		{ "text",  NULL },
+		{ "from",  from },
+		{ NULL,    NULL },
+	};
+
+	for ( node = n -> children; node; node = node -> next ) {
+		const gchar *name = node -> name;
+
+		if ( ! strcmp ( name, "text" ) )
+			args[2].value = lm_message_node_get_value ( node );
+		else {
+			LmMessageNode *mnode = node -> children;
+
+			if ( mnode ) // XXX check for xmlns presence?
+				args[1].value = mnode -> name;
+
+			args[0].value = name;
+		}
+	}
+
+	hk_run_handlers ( HOOK_ACTIVITY_IN, args );
+}
+
+static guint activity_hch ( const gchar *hid, hk_arg_t *args, gpointer userdata )
+{
+	if (activity_delayed) {
+		gchar *tmp_major = activity_major;
+		gchar *tmp_minor = activity_minor;
+		gchar *tmp_text  = activity_text;
+
+		activity_delayed = FALSE;
+		activity_major = activity_minor = activity_text = NULL;
+
+		activity_publish (tmp_major, tmp_minor, tmp_text);
+
+		g_free (tmp_major);
+		g_free (tmp_minor);
+		g_free (tmp_text);
+	}
+
+	return HOOK_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+}
+
+static guint activity_hdh ( const gchar *hid, hk_arg_t *args, gpointer userdata )
+{
+#ifdef HAVE_LM_CONNECTION_UNREGISTER_REPLY_HANDLER
+	if ( lconnection && activity_reply_handler )
+		lm_connection_unregister_reply_handler ( lconnection, activity_reply_handler );
+#endif
+
+	return HOOK_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+}
+
+static guint activity_haoh ( const gchar *hid, hk_arg_t *args, gpointer userdata )
+{
+	const gchar *minor = NULL;
+	const gchar *major = NULL;
+	const gchar *text  = NULL;
+
+	{
+		hk_arg_t *arg;
+		for ( arg = args; arg -> name; arg ++ ) {
+			const gchar *value = arg -> value;
+			if ( value ) {
+				const gchar *name = arg -> name;
+				if ( ! strcmp ( name, "major" ) )
+					major = value;
+				else if ( ! strcmp ( name, "minor" ) )
+					minor = value;
+				else if ( !strcmp ( name, "text" ) )
+					text = value;
+			}
+		}
+	}
+
+	activity_publish ( major, minor, text );
+
+	return HOOK_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+}
+
+void pep_activity_init (void)
+{
+	activity_gerror_quark = g_quark_from_string ( "pep-activity-gerror-quark" );
+
+	activity_reply_handler = lm_message_handler_new (activity_publish_reply_handler, NULL, NULL);
+
+	pep_register_xmlns_handler (NS_ACTIVITY, activity_handler, NULL, NULL);
+
+	activity_hid_connect     = hk_add_handler (activity_hch,  HOOK_POST_CONNECT, G_PRIORITY_DEFAULT, NULL);
+	activity_hid_disconnect  = hk_add_handler (activity_hdh,  HOOK_PRE_DISCONNECT, G_PRIORITY_DEFAULT, NULL);
+	activity_hid_activityout = hk_add_handler (activity_haoh, HOOK_ACTIVITY_OUT, G_PRIORITY_DEFAULT, NULL);
+
+	xmpp_add_feature ( NS_ACTIVITY        );
+	xmpp_add_feature ( NS_ACTIVITY_NOTIFY );
+}
+
+void pep_activity_uninit (void)
+{
+	xmpp_del_feature ( NS_ACTIVITY        );
+	xmpp_del_feature ( NS_ACTIVITY_NOTIFY );
+
+	hk_del_handler ( HOOK_POST_CONNECT,   activity_hid_connect );
+	hk_del_handler ( HOOK_PRE_DISCONNECT, activity_hid_disconnect );
+	hk_del_handler ( HOOK_ACTIVITY_OUT,   activity_hid_activityout );
+
+	pep_unregister_xmlns_handler ( NS_ACTIVITY );
+
+#ifdef HAVE_LM_CONNECTION_UNREGISTER_REPLY_HANDLER
+	if (lconnection)
+		lm_connection_unregister_reply_handler (lconnection, activity_reply_handler);
+#endif
+	lm_message_handler_invalidate (activity_reply_handler);
+	lm_message_handler_unref (activity_reply_handler);
+
+	g_free (activity_major);
+	g_free (activity_minor);
+	g_free (activity_text);
+}
+
+/* vim: se ts=4 sw=4: */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pep_geoloc.c	Sun May 20 22:15:51 2012 +0300
@@ -0,0 +1,474 @@
+/*
+ * pep_geoloc.c         -- Pep geographical location events
+ *
+ * Copyright (C) 2009-2012 Myhailo Danylenko <isbear@ukrpost.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <glib.h>
+#include <loudmouth/loudmouth.h>
+#include <stdlib.h>                 // atoi
+#include <string.h>
+#include <time.h>
+
+#include <mcabber/logprint.h>
+#include <mcabber/utils.h>
+#include <mcabber/xmpp.h>
+#include <mcabber/xmpp_helper.h>
+#include <mcabber/hooks.h>
+#include <mcabber/settings.h>
+#include <mcabber/modules.h>
+
+#include "pep.h"
+#include "geoloc.h"
+
+#include "config.h"
+
+//
+//  module description
+//
+
+void pep_geoloc_init   (void);
+void pep_geoloc_uninit (void);
+
+#define DESCRIPTION ( \
+	"PEP geoloc event handling\n" \
+	"Recognizes option geoloc_interval" )
+
+static const gchar *deps[] = { "pep", NULL };
+
+static module_info_t info_geoloc_dev = {
+	.branch      = "dev",
+	.api         = 20,
+	.version     = PROJECT_VERSION,
+	.description = DESCRIPTION,
+	.requires    = deps,
+	.init        = pep_geoloc_init,
+	.uninit      = pep_geoloc_uninit,
+	.next        = NULL,
+};
+
+module_info_t info_pep_geoloc = {
+	.branch      = "0.10.1",
+	.api         = 1,
+	.version     = PROJECT_VERSION,
+	.description = DESCRIPTION,
+	.requires    = deps,
+	.init        = pep_geoloc_init,
+	.uninit      = pep_geoloc_uninit,
+	.next        = &info_geoloc_dev,
+};
+
+//
+//  globals
+//
+
+#define MAX_NO ( 21 )
+
+static geoloc_pair_t info[] = {
+	{ "accuracy",    NULL },
+	{ "alt",         NULL },
+	{ "area",        NULL },
+	{ "bearing",     NULL },
+	{ "building",    NULL },
+	{ "country",     NULL },
+	{ "countrycode", NULL },
+	{ "datum",       NULL },
+	{ "description", NULL },
+	{ "error",       NULL },
+	{ "floor",       NULL },
+	{ "lat",         NULL },
+	{ "locality",    NULL },
+	{ "lon",         NULL },
+	{ "postalcode",  NULL },
+	{ "region",      NULL },
+	{ "room",        NULL },
+	{ "speed",       NULL },
+	{ "street",      NULL },
+	{ "text",        NULL },
+	{ "timestamp",   NULL },
+	{ "uri",         NULL },
+	{ NULL,          NULL },
+};
+
+static GQuark            geoloc_gerror_quark    = 0;
+static gboolean          publish_delayed        = FALSE;
+static guint             geoloc_hid_connect     = 0;
+static guint             geoloc_hid_disconnect  = 0;
+static guint             geoloc_hid_geolocout   = 0;
+static guint             geoloc_interval        = 0;
+static time_t            geoloc_timestamp       = 0;
+static LmMessageHandler *geoloc_reply_handler   = NULL;
+static guint             geoloc_source          = 0;
+static gboolean          geoloc_guard_installed = FALSE;
+
+//
+//  predeclarations
+//
+
+static void geoloc_publish_info (void);
+
+//
+//  code
+//
+
+static LmHandlerResult geoloc_publish_reply_handler (LmMessageHandler *handler, LmConnection *connection, LmMessage *message, gpointer userdata)
+{
+	switch (lm_message_get_sub_type (message)) {
+	case LM_MESSAGE_SUB_TYPE_RESULT:
+		break;
+	
+	case LM_MESSAGE_SUB_TYPE_ERROR:
+		
+		{
+			LmMessageNode *node   = lm_message_get_node (message);
+			const gchar   *type;
+			const gchar   *reason;
+
+			node = lm_message_node_get_child (node, "error");
+			type = lm_message_node_get_attribute (node, "type");
+			if (node->children)
+				reason = node->children->name;
+			else
+				reason = "undefined";
+
+			scr_log_print (LPRINT_LOGNORM, "geoloc: Publish failed: %s - %s.", type, reason);
+		}
+
+		break;
+	
+	default:
+		return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+		break;
+	}
+
+	return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+}
+
+static gboolean geoloc_delayed_publish_cb (gpointer data)
+{
+	geoloc_source = 0;
+	geoloc_publish_info ();
+	return FALSE;
+}
+
+static void geoloc_publish_info (void)
+{
+	if (!xmpp_is_online ()) {
+		scr_log_print (LPRINT_DEBUG, "geoloc: Not online, delaying publish.");
+		publish_delayed = TRUE;
+		return;
+	}
+
+	// check for frequency of publishes
+	if (geoloc_interval) {
+		time_t now = time (NULL);
+
+		if (now - geoloc_timestamp < geoloc_interval) {
+
+			scr_log_print (LPRINT_DEBUG, "geoloc: Publish interval not passed, delaying.");
+			if (!geoloc_source)
+				geoloc_source = g_timeout_add_seconds ( geoloc_interval - ( now - geoloc_timestamp ), geoloc_delayed_publish_cb, NULL );
+			return;
+
+		} else
+			geoloc_timestamp = now;
+	}
+
+	LmMessage     *request = lm_message_new_with_sub_type (NULL, LM_MESSAGE_TYPE_IQ, LM_MESSAGE_SUB_TYPE_SET);
+	LmMessageNode *node    = lm_message_get_node (request);
+	lm_message_node_set_attribute (node, "from", lm_connection_get_jid (lconnection));
+
+	node = lm_message_node_add_child (node, "pubsub", NULL);
+	lm_message_node_set_attribute (node, "xmlns", NS_PUBSUB);
+
+	node = lm_message_node_add_child (node, "publish", NULL);
+	lm_message_node_set_attribute (node, "node", NS_GEOLOC);
+
+	node = lm_message_node_add_child (node, "item", NULL);
+
+	node = lm_message_node_add_child (node, "geoloc", NULL);
+	lm_message_node_set_attribute (node, "xmlns", NS_GEOLOC);
+
+	{ // put data inside
+		int i;
+
+		for ( i = 0; i <= MAX_NO; ++ i )
+			if ( info[i].value )
+				lm_message_node_add_child ( node, info[i].name, info[i].value );
+	}
+
+	{ // send
+		GError *error = NULL;
+
+		lm_connection_send_with_reply (lconnection, request, geoloc_reply_handler, &error);
+
+		if (error) {
+			scr_log_print (LPRINT_DEBUG, "geoloc: Publishing error: %s.", error -> message);
+			g_error_free (error);
+		}
+	}
+
+	lm_message_unref (request);
+	//publish_delayed = FALSE; XXX
+}
+
+void geoloc_publish (const geoloc_pair_t *pairs)
+{
+	gboolean              publish    = FALSE;
+	const geoloc_pair_t  *tag;
+	static geoloc_pair_t  new_info[] = {
+		{ "accuracy",    NULL },
+		{ "alt",         NULL },
+		{ "area",        NULL },
+		{ "bearing",     NULL },
+		{ "building",    NULL },
+		{ "country",     NULL },
+		{ "countrycode", NULL },
+		{ "datum",       NULL },
+		{ "description", NULL },
+		{ "error",       NULL },
+		{ "floor",       NULL },
+		{ "lat",         NULL },
+		{ "locality",    NULL },
+		{ "lon",         NULL },
+		{ "postalcode",  NULL },
+		{ "region",      NULL },
+		{ "room",        NULL },
+		{ "speed",       NULL },
+		{ "street",      NULL },
+		{ "text",        NULL },
+		{ "timestamp",   NULL },
+		{ "uri",         NULL },
+		{ NULL,          NULL },
+	};
+
+	// populate new_info with new values
+	for (tag = pairs; tag->name; ++tag) {
+		int i;
+		for (i = 0; i <= MAX_NO; ++i)
+			if (!g_strcmp0 (tag->name, new_info[i].name))
+				new_info[i].value = tag->value;
+	}
+
+	{ // check, if it differ from info
+		int i;
+		for (i = 0; i <= MAX_NO; ++i)
+			if (g_strcmp0 (new_info[i].value, info[i].value)) {
+				publish = TRUE;
+				break;
+			}
+	}
+
+	if (publish) {
+
+		{ // copy new vaules to info
+			int i;
+			for (i = 0; i <= MAX_NO; ++i) {
+				if (info[i].value)
+					g_free (info[i].value);
+				info[i].value = g_strdup (new_info[i].value);
+			}
+		}
+
+		geoloc_publish_info ();
+	}
+}
+
+gboolean geoloc_request ( const gchar *to, GError **err )
+{
+
+	LmMessage     *request;
+	LmMessageNode *node;
+
+	if (!xmpp_is_online ()) {
+		g_set_error ( err, geoloc_gerror_quark, GEOLOC_ERROR_NOTCONNECTED, "You are not connected" );
+		return FALSE;
+	}
+
+	request = lm_message_new_with_sub_type (to, LM_MESSAGE_TYPE_IQ, LM_MESSAGE_SUB_TYPE_GET);
+	node = lm_message_get_node (request);
+	lm_message_node_set_attribute (node, "from", lm_connection_get_jid (lconnection));
+
+	node = lm_message_node_add_child (node, "pubsub", NULL);
+	lm_message_node_set_attribute (node, "xmlns", NS_PUBSUB);
+
+	node = lm_message_node_add_child (node, "items", NULL);
+	lm_message_node_set_attribute (node, "node", NS_GEOLOC);
+
+	{ // send, result will be handled by pep
+		GError *error = NULL;
+
+		lm_connection_send (lconnection, request, &error);
+
+		if (error) {
+			g_propagate_error ( err, error );
+			return FALSE;
+		}
+	}
+	
+	lm_message_unref (request);
+
+	return TRUE;
+}
+
+static void geoloc_handler (const gchar *from, const gchar *node, LmMessageNode *n, const gchar *id, gpointer ignore)
+{
+	LmMessageNode *tag;
+	hk_arg_t args[] = {
+		{ "accuracy",    NULL },
+		{ "alt",         NULL },
+		{ "area",        NULL },
+		{ "bearing",     NULL },
+		{ "building",    NULL },
+		{ "country",     NULL },
+		{ "countrycode", NULL },
+		{ "datum",       NULL },
+		{ "description", NULL },
+		{ "error",       NULL },
+		{ "floor",       NULL },
+		{ "lat",         NULL },
+		{ "locality",    NULL },
+		{ "lon",         NULL },
+		{ "postalcode",  NULL },
+		{ "region",      NULL },
+		{ "room",        NULL },
+		{ "speed",       NULL },
+		{ "street",      NULL },
+		{ "text",        NULL },
+		{ "timestamp",   NULL },
+		{ "uri",         NULL },
+		{ "from",        from },
+		{ NULL,          NULL },
+	};
+
+	for ( tag = n -> children; tag; tag = tag -> next ) {
+		const gchar *name = tag -> name;
+		if ( name ) {
+			int i;
+			for ( i = 0; i <= MAX_NO; ++ i )
+				if ( ! strcmp ( name, args[i].name ) ) {
+					const gchar *value = lm_message_node_get_value ( tag );
+					if ( value )
+						args[i].value = value;
+				}
+		}
+	}
+
+	hk_run_handlers ( HOOK_GEOLOC_IN, args );
+}
+
+static guint geoloc_hch (const gchar *hid, hk_arg_t *args, gpointer userdata)
+{
+	if (publish_delayed) {
+		scr_log_print (LPRINT_DEBUG, "geoloc: Publishing delayed data.");
+
+		publish_delayed = FALSE;
+		geoloc_publish_info ();
+	}
+
+	return HOOK_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+}
+
+static guint geoloc_hdh (const gchar *hid, hk_arg_t *args, gpointer userdata)
+{
+#ifdef HAVE_LM_CONNECTION_UNREGISTER_REPLY_HANDLER
+		if (lconnection)
+			lm_connection_unregister_reply_handler (lconnection, geoloc_reply_handler);
+#endif
+
+	return HOOK_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+}
+
+static guint geoloc_hgoh ( const gchar *hid, hk_arg_t *args, gpointer userdata )
+{
+	geoloc_publish ( (const geoloc_pair_t *) args );
+	return HOOK_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+}
+
+static gchar *geoloc_guard (const char *key, const char *new_value)
+{
+	if (new_value)
+		geoloc_interval = atoi (new_value);
+	else
+		geoloc_interval = 0;
+	
+	if (geoloc_source) {
+		g_source_remove (geoloc_source);
+		geoloc_source = 0;
+		// Will reinstall source, if necessary
+		geoloc_publish_info ();
+	}
+
+	return g_strdup (new_value);
+}
+
+void pep_geoloc_init (void)
+{
+	geoloc_gerror_quark = g_quark_from_string ( "pep-geoloc-gerror-quark" );
+
+	geoloc_interval = settings_opt_get_int ("geoloc_interval");
+
+	geoloc_guard_installed = settings_set_guard ("geoloc_interval", geoloc_guard);
+	if (!geoloc_guard_installed)
+		scr_log_print (LPRINT_LOGNORM, "geoloc: Warning: cannot install option guard for 'geoloc_interval'.");
+
+	pep_register_xmlns_handler (NS_GEOLOC, geoloc_handler, NULL, NULL);
+
+	geoloc_reply_handler = lm_message_handler_new (geoloc_publish_reply_handler, NULL, NULL);
+
+	geoloc_hid_connect    = hk_add_handler ( geoloc_hch,  HOOK_POST_CONNECT, G_PRIORITY_DEFAULT, NULL );
+	geoloc_hid_disconnect = hk_add_handler ( geoloc_hdh,  HOOK_PRE_DISCONNECT, G_PRIORITY_DEFAULT, NULL );
+	geoloc_hid_geolocout  = hk_add_handler ( geoloc_hgoh, HOOK_GEOLOC_OUT, G_PRIORITY_DEFAULT, NULL );
+
+	xmpp_add_feature ( NS_GEOLOC        );
+	xmpp_add_feature ( NS_GEOLOC_NOTIFY );
+}
+
+void pep_geoloc_uninit (void)
+{
+	xmpp_del_feature ( NS_GEOLOC        );
+	xmpp_del_feature ( NS_GEOLOC_NOTIFY );
+
+	hk_del_handler ( HOOK_POST_CONNECT,   geoloc_hid_connect    );
+	hk_del_handler ( HOOK_PRE_DISCONNECT, geoloc_hid_disconnect );
+	hk_del_handler ( HOOK_GEOLOC_OUT,     geoloc_hid_geolocout  );
+
+	if (geoloc_source)
+		g_source_remove (geoloc_source);
+
+	pep_unregister_xmlns_handler ( NS_GEOLOC );
+
+	lm_message_handler_invalidate (geoloc_reply_handler);
+	lm_message_handler_unref (geoloc_reply_handler);
+#ifdef HAVE_LM_CONNECTION_UNREGISTE_REPLY_HANDLER
+	if (lconnection)
+		lm_connection_unregister_reply_handler (lconnection, handler);
+#endif
+
+	{
+		int i;
+		for (i = 0; i <= MAX_NO; ++i)
+			if (info[i].value)
+				g_free (info[i].value);
+	}
+
+	if (geoloc_guard_installed)
+		settings_del_guard ("geoloc_interval");
+}
+
+/* vim: se ts=4 sw=4: */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pep_mood.c	Sun May 20 22:15:51 2012 +0300
@@ -0,0 +1,321 @@
+/*
+ * pep_mood.c           -- Pep mood events
+ *
+ * Copyright (C) 2009-2012 Myhailo Danylenko <isbear@ukrpost.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <glib.h>
+#include <loudmouth/loudmouth.h>
+#include <string.h>
+
+#include <mcabber/utils.h>
+#include <mcabber/xmpp.h>
+#include <mcabber/xmpp_helper.h>
+#include <mcabber/logprint.h>
+#include <mcabber/hooks.h>
+#include <mcabber/modules.h>
+
+#include "pep.h"
+#include "mood.h"
+
+#include "config.h"
+
+//
+//  module description
+//
+
+void pep_mood_init   (void);
+void pep_mood_uninit (void);
+
+#define DESCRIPTION ( "PEP mood events handling" )
+
+static const gchar *deps[] = { "pep", NULL };
+
+static module_info_t info_mood_dev = {
+	.branch      = "dev",
+	.api         = 20,
+	.version     = PROJECT_VERSION,
+	.description = DESCRIPTION,
+	.requires    = deps,
+	.init        = pep_mood_init,
+	.uninit      = pep_mood_uninit,
+	.next        = NULL,
+};
+
+module_info_t info_pep_mood = {
+	.branch      = "0.10.1",
+	.api         = 1,
+	.version     = PROJECT_VERSION,
+	.description = DESCRIPTION,
+	.requires    = deps,
+	.init        = pep_mood_init,
+	.uninit      = pep_mood_uninit,
+	.next        = &info_mood_dev,
+};
+
+//
+//  globals
+//
+
+static GQuark            mood_gerror_quark   = 0;
+static guint             mood_hid_connect    = 0;
+static guint             mood_hid_disconnect = 0;
+static guint             mood_hid_moodout    = 0;
+static LmMessageHandler *mood_reply_handler  = NULL;
+static gboolean          publish_delayed     = FALSE;
+static gchar            *delayed_mood        = NULL;
+static gchar            *delayed_text        = NULL;
+
+//
+//  code
+//
+
+static LmHandlerResult mood_publish_reply_handler (LmMessageHandler *handler, LmConnection *connection, LmMessage *message, gpointer userdata)
+{
+	switch (lm_message_get_sub_type (message)) {
+	case LM_MESSAGE_SUB_TYPE_RESULT:
+		break;
+	
+	case LM_MESSAGE_SUB_TYPE_ERROR:
+		
+		{
+			LmMessageNode *node   = lm_message_get_node (message);
+			const gchar   *type;
+			const gchar   *reason;
+
+			node = lm_message_node_get_child (node, "error");
+			type = lm_message_node_get_attribute (node, "type");
+			if (node->children)
+				reason = node->children->name;
+			else
+				reason = "undefined";
+
+			scr_log_print (LPRINT_LOGNORM, "mood: Publish failed: %s - %s", type, reason);
+		}
+
+		break;
+	
+	default:
+		return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+		break;
+	}
+
+	return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+}
+
+void mood_publish (const gchar *mood, const gchar *text)
+{
+	if (!xmpp_is_online ()) {
+		scr_log_print (LPRINT_DEBUG, "mood: Delaying publish until online.");
+
+		g_free (delayed_mood);
+		g_free (delayed_text);
+
+		delayed_mood = g_strdup (mood);
+		delayed_text = g_strdup (text);
+		publish_delayed = TRUE;
+
+		return;
+	}
+
+	LmMessage     *request = lm_message_new_with_sub_type (NULL, LM_MESSAGE_TYPE_IQ, LM_MESSAGE_SUB_TYPE_SET);
+	LmMessageNode *node    = lm_message_get_node (request);
+	lm_message_node_set_attribute (node, "from", lm_connection_get_jid (lconnection));
+
+	node = lm_message_node_add_child (node, "pubsub", NULL);
+	lm_message_node_set_attribute (node, "xmlns", NS_PUBSUB);
+
+	node = lm_message_node_add_child (node, "publish", NULL);
+	lm_message_node_set_attribute (node, "node", NS_MOOD);
+
+	node = lm_message_node_add_child (node, "item", NULL);
+
+	node = lm_message_node_add_child (node, "mood", NULL);
+	lm_message_node_set_attribute (node, "xmlns", NS_MOOD);
+
+	if (mood)
+		lm_message_node_add_child (node, mood, NULL);
+	if (text)
+		lm_message_node_add_child (node, "text", text);
+
+	{ // send
+		GError *error = NULL;
+
+		lm_connection_send_with_reply (lconnection, request, mood_reply_handler, &error);
+
+		if (error) {
+			scr_log_print (LPRINT_DEBUG, "mood: Publish sending error: %s.", error -> message);
+			g_error_free (error);
+		}
+	}
+
+	lm_message_unref (request);
+}
+
+gboolean mood_request ( const gchar *to, GError **err )
+{
+	LmMessage *request;
+	LmMessageNode *node;
+	
+	if (!xmpp_is_online ()) {
+		g_set_error ( err, mood_gerror_quark, MOOD_ERROR_NOTCONNECTED, "You are not connected" );
+		return FALSE;
+	}
+
+	request = lm_message_new_with_sub_type (to, LM_MESSAGE_TYPE_IQ, LM_MESSAGE_SUB_TYPE_GET);
+	node = lm_message_get_node (request);
+	lm_message_node_set_attribute (node, "from", lm_connection_get_jid (lconnection));
+
+	node = lm_message_node_add_child (node, "pubsub", NULL);
+	lm_message_node_set_attribute (node, "xmlns", NS_PUBSUB);
+
+	node = lm_message_node_add_child (node, "items", NULL);
+	lm_message_node_set_attribute (node, "node", NS_MOOD);
+
+	{ // send, result will be handled by pep
+		GError *error = NULL;
+
+		lm_connection_send (lconnection, request, &error);
+
+		if (error) {
+			g_propagate_error ( err, error );
+			return FALSE;
+		}
+	}
+
+	lm_message_unref (request);
+
+	return TRUE;
+}
+
+static void mood_handler ( const gchar *from, const gchar *enode, LmMessageNode *n, const gchar *id, gpointer ignore )
+{
+	LmMessageNode *node;
+	hk_arg_t args[] = {
+		{ "mood", NULL },
+		{ "text", NULL },
+		{ "from", from },
+		{ NULL,   NULL },
+	};
+
+	for ( node = n -> children; node; node = node -> next ) {
+		const gchar *name = node -> name;
+
+		if ( ! strcmp ( name, "text" ) )
+			args[1].value = lm_message_node_get_value ( node );
+		else
+			args[0].value = name;
+	}
+
+	hk_run_handlers ( HOOK_MOOD_IN, args );
+}
+
+static guint mood_hch (const gchar *hid, hk_arg_t *args, gpointer userdata)
+{
+	if (publish_delayed) {
+
+		char *tmp_mood = delayed_mood;
+		char *tmp_text = delayed_text;
+
+		scr_log_print (LPRINT_DEBUG, "mood: Publishing delayed data.");
+
+		delayed_mood = delayed_text = NULL;
+		publish_delayed = FALSE;
+
+		mood_publish (tmp_mood, tmp_text);
+
+		g_free (tmp_mood);
+		g_free (tmp_text);
+	}
+
+	return HOOK_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+}
+
+static guint mood_hdh (const gchar *hid, hk_arg_t *args, gpointer userdata)
+{
+#ifdef HAVE_LM_CONNECTION_UNREGISTER_REPLY_HANDLER
+	if (lconnection && mood_reply_handler)
+		lm_connection_unregister_reply_handler (lconnection, mood_reply_handler);
+#endif
+
+	return HOOK_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+}
+
+static guint mood_hmoh ( const gchar *hid, hk_arg_t *args, gpointer userdata )
+{
+	const gchar *mood = NULL;
+	const gchar *text = NULL;
+
+	{
+		hk_arg_t *arg;
+		for ( arg = args; arg -> name; arg ++ ) {
+			const gchar *value = arg -> value;
+			if ( value ) {
+				const gchar *name = arg -> name;
+				if ( ! strcmp ( name, "mood" ) )
+					mood = value;
+				else if ( ! strcmp ( name, "text" ) )
+					text = value;
+			}
+		}
+	}
+
+	mood_publish ( mood, text );
+
+	return HOOK_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+}
+
+void pep_mood_init (void)
+{
+	mood_gerror_quark = g_quark_from_string ( "pep-mood-gerror-quark" );
+
+	mood_reply_handler = lm_message_handler_new (mood_publish_reply_handler, NULL, NULL);
+
+	pep_register_xmlns_handler (NS_MOOD, mood_handler, NULL, NULL);
+
+	mood_hid_connect    = hk_add_handler ( mood_hch,  HOOK_POST_CONNECT, G_PRIORITY_DEFAULT, NULL );
+	mood_hid_disconnect = hk_add_handler ( mood_hdh,  HOOK_PRE_DISCONNECT, G_PRIORITY_DEFAULT, NULL );
+	mood_hid_moodout    = hk_add_handler ( mood_hmoh, HOOK_MOOD_OUT, G_PRIORITY_DEFAULT, NULL );
+
+	xmpp_add_feature ( NS_MOOD        );
+	xmpp_add_feature ( NS_MOOD_NOTIFY );
+}
+
+void pep_mood_uninit (void)
+{
+	xmpp_del_feature ( NS_MOOD        );
+	xmpp_del_feature ( NS_MOOD_NOTIFY );
+
+	hk_del_handler ( HOOK_POST_CONNECT,   mood_hid_connect );
+	hk_del_handler ( HOOK_PRE_DISCONNECT, mood_hid_disconnect );
+	hk_del_handler ( HOOK_MOOD_OUT,       mood_hid_moodout );
+
+	pep_unregister_xmlns_handler (NS_MOOD);
+
+#ifdef HAVE_LM_CONNECTION_UNREGISTER_REPLY_HANDLER
+	if (lconnection && mood_reply_handler)
+		lm_connection_unregister_reply_handler (lconnection, mood_reply_handler);
+#endif
+	lm_message_handler_invalidate (mood_reply_handler);
+	lm_message_handler_unref (mood_reply_handler);
+
+	g_free ( delayed_mood );
+	g_free ( delayed_text );
+}
+
+/* vim: se ts=4 sw=4: */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pep_tune.c	Sun May 20 22:15:51 2012 +0300
@@ -0,0 +1,437 @@
+
+/* Copyright 2009-2012 Myhailo Danylenko
+ *
+ * This file is part of mcabber-pep
+ *
+ * mcabber-pep 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, see <http://www.gnu.org/licenses/>. */
+
+#include <glib.h>
+#include <loudmouth/loudmouth.h>
+#include <stdlib.h>                 // atoi
+#include <time.h>
+
+#include <mcabber/settings.h>
+#include <mcabber/utils.h>
+#include <mcabber/xmpp.h>
+#include <mcabber/xmpp_helper.h>
+#include <mcabber/logprint.h>
+#include <mcabber/hooks.h>
+#include <mcabber/modules.h>
+
+#include "pep.h"
+#include "tune.h"
+
+#include "config.h"
+
+//
+//  module description
+//
+
+void pep_tune_init   (void);
+void pep_tune_uninit (void);
+
+#define DESCRIPTION ( \
+	"PEP tune event handler\n" \
+	"Recognizes option tune_interval" )
+
+static const gchar *deps[] = { "pep", NULL };
+
+static module_info_t info_tune_dev = {
+	.branch      = "dev",
+	.api         = 20,
+	.version     = PROJECT_VERSION,
+	.description = DESCRIPTION,
+	.requires    = deps,
+	.init        = pep_tune_init,
+	.uninit      = pep_tune_uninit,
+	.next        = NULL,
+};
+
+static module_info_t info_tune_0_10_0 = {
+	.branch      = "0.10.0",
+	.api         = 1,
+	.version     = PROJECT_VERSION,
+	.description = DESCRIPTION,
+	.requires    = deps,
+	.init        = pep_tune_init,
+	.uninit      = pep_tune_uninit,
+	.next        = &info_tune_dev,
+};
+
+module_info_t info_pep_tune = {
+	.branch      = "0.10.1",
+	.api         = 1,
+	.version     = PROJECT_VERSION,
+	.description = DESCRIPTION,
+	.requires    = deps,
+	.init        = pep_tune_init,
+	.uninit      = pep_tune_uninit,
+	.next        = &info_tune_0_10_0,
+};
+
+//
+//  globals
+//
+
+#define MAX_NO ( 6 )
+
+static tune_pair_t info[] = {
+	{ "artist", NULL },
+	{ "length", NULL },
+	{ "rating", NULL },
+	{ "source", NULL },
+	{ "title",  NULL },
+	{ "track",  NULL },
+	{ "uri",    NULL },
+	{ NULL,     NULL },
+};
+
+static GQuark            tune_gerror_quark    = 0;
+static gboolean          publish_delayed      = FALSE;
+static guint             tune_hid_connect     = 0;
+static guint             tune_hid_disconnect  = 0;
+static guint             tune_hid_tuneout     = 0;
+static guint             tune_interval        = 0;
+static time_t            tune_timestamp       = 0;
+static LmMessageHandler *tune_reply_handler   = NULL;
+static guint             tune_source          = 0;
+static gboolean          tune_guard_installed = FALSE;
+
+//
+//  predeclarations
+//
+
+static void tune_publish_info (void);
+
+//
+//  code
+//
+
+static LmHandlerResult tune_publish_reply_handler (LmMessageHandler *handler, LmConnection *connection, LmMessage *message, gpointer userdata)
+{
+	switch (lm_message_get_sub_type (message)) {
+	case LM_MESSAGE_SUB_TYPE_RESULT:
+		break;
+	
+	case LM_MESSAGE_SUB_TYPE_ERROR:
+		
+		{
+			LmMessageNode *node   = lm_message_get_node (message);
+			const gchar   *type;
+			const gchar   *reason;
+
+			node = lm_message_node_get_child (node, "error");
+			type = lm_message_node_get_attribute (node, "type");
+			if (node->children)
+				reason = node->children->name;
+			else
+				reason = "undefined";
+
+			scr_log_print (LPRINT_LOGNORM, "tune: Publish failed: %s - %s", type, reason);
+		}
+
+		break;
+	
+	default:
+		return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+		break;
+	}
+
+	return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+}
+
+static gboolean tune_delayed_publish_cb (gpointer data)
+{
+	tune_source = 0;
+	tune_publish_info ();
+	return FALSE;
+}
+
+static void tune_publish_info (void)
+{
+	if (!xmpp_is_online ()) {
+		scr_log_print (LPRINT_DEBUG, "tune: Not online, delaying publish.");
+		publish_delayed = TRUE;
+		return;
+	}
+
+	// check for frequency of publihes
+	if (tune_interval) {
+		time_t now = time (NULL);
+
+		if (now - tune_timestamp < tune_interval) {
+
+			scr_log_print (LPRINT_DEBUG, "tune: Publish interval not passed, delaying publish.");
+			if (!tune_source)
+				tune_source = g_timeout_add_seconds ( tune_interval - ( now - tune_timestamp ), tune_delayed_publish_cb, NULL );
+			return;
+
+		} else
+			tune_timestamp = now;
+	}
+
+	// publish
+	LmMessage     *request = lm_message_new_with_sub_type (NULL, LM_MESSAGE_TYPE_IQ, LM_MESSAGE_SUB_TYPE_SET);
+	LmMessageNode *node    = lm_message_get_node (request);
+	lm_message_node_set_attribute (node, "from", lm_connection_get_jid (lconnection));
+
+	node = lm_message_node_add_child (node, "pubsub", NULL);
+	lm_message_node_set_attribute (node, "xmlns", NS_PUBSUB);
+
+	node = lm_message_node_add_child (node, "publish", NULL);
+	lm_message_node_set_attribute (node, "node", NS_TUNE);
+
+	node = lm_message_node_add_child (node, "item", NULL);
+
+	node = lm_message_node_add_child (node, "tune", NULL);
+	lm_message_node_set_attribute (node, "xmlns", NS_TUNE);
+
+	{ // put data inside
+		int i;
+
+		for (i = 0; i <= MAX_NO; ++i)
+			if (info[i].value)
+				lm_message_node_add_child (node, info[i].name, info[i].value);
+	}
+
+	{ // send
+		GError *error = NULL;
+
+		lm_connection_send_with_reply (lconnection, request, tune_reply_handler, &error);
+
+		if (error) {
+			scr_log_print (LPRINT_DEBUG, "tune: Publishing error: %s.", error -> message);
+			g_error_free (error);
+		}
+	}
+
+	lm_message_unref (request);
+}
+
+void tune_publish (const tune_pair_t *pairs)
+{
+	gboolean           publish    = FALSE;
+	const tune_pair_t *tag;
+	tune_pair_t        new_info[] = {
+		{ "artist", NULL },
+		{ "length", NULL },
+		{ "rating", NULL },
+		{ "source", NULL },
+		{ "title",  NULL },
+		{ "track",  NULL },
+		{ "uri",    NULL },
+		{ NULL,     NULL },
+	};
+
+	// populate new_info with new values
+	for (tag = pairs; tag->name; ++tag) {
+		int i;
+		for (i = 0; i <= MAX_NO; ++i)
+			if (!g_strcmp0 (tag->name, new_info[i].name))
+				new_info[i].value = tag->value;
+	}
+
+	{ // check, if it differ from info
+		int i;
+		for (i = 0; i <= MAX_NO; ++i)
+			if (g_strcmp0 (new_info[i].value, info[i].value)) {
+				publish = TRUE;
+				break;
+			}
+	}
+
+	if (publish) {
+
+		{ // copy new values to info
+			int i;
+			for (i = 0; i <= MAX_NO; ++i) {
+				if (info[i].value)
+					g_free (info[i].value);
+				info[i].value = g_strdup (new_info[i].value);
+			}
+		}
+
+		tune_publish_info ();
+	}
+}
+
+gboolean tune_request ( const gchar *to, GError **err )
+{
+	LmMessage *request;
+	LmMessageNode *node;
+
+	if (!xmpp_is_online ()) {
+		g_set_error ( err, tune_gerror_quark, TUNE_ERROR_NOTCONNECTED, "You are not connected" );
+		return FALSE;
+	}
+
+	request = lm_message_new_with_sub_type ( to, LM_MESSAGE_TYPE_IQ, LM_MESSAGE_SUB_TYPE_GET );
+	node = lm_message_get_node (request);
+	lm_message_node_set_attribute (node, "from", lm_connection_get_jid (lconnection));
+
+	node = lm_message_node_add_child (node, "pubsub", NULL);
+	lm_message_node_set_attribute (node, "xmlns", NS_PUBSUB);
+
+	node = lm_message_node_add_child (node, "items", NULL);
+	lm_message_node_set_attribute (node, "node", NS_TUNE);
+
+	{ // send, result will be handled by pep
+		GError *error = NULL;
+
+		lm_connection_send ( lconnection, request, &error );
+
+		if ( error ) {
+			g_propagate_error ( err, error );
+			return FALSE;
+		}
+	}
+
+	lm_message_unref (request);
+
+	return TRUE;
+}
+
+static void tune_handler ( const gchar *from, const gchar *node, LmMessageNode *n, const gchar *id, gpointer ignore )
+{
+	LmMessageNode *tag;
+	hk_arg_t args[] = {
+		{ "artist", NULL },
+		{ "length", NULL },
+		{ "rating", NULL },
+		{ "source", NULL },
+		{ "title",  NULL },
+		{ "track",  NULL },
+		{ "uri",    NULL },
+		{ "from",   from },
+		{ NULL,     NULL },
+	};
+
+	for ( tag = n -> children; tag; tag = tag -> next ) {
+		const gchar *name  = tag -> name;
+		if ( name ) {
+			int i;
+			for ( i = 0; i <= MAX_NO; ++i )
+				if ( ! g_strcmp0 ( name, args[i].name ) ) {
+					const gchar *value = lm_message_node_get_value ( tag );
+					if ( value )
+						args[i].value = value;
+				}
+		}
+	}
+
+	hk_run_handlers ( HOOK_TUNE_IN, args );
+}
+
+static guint tune_hch (const gchar *htype, hk_arg_t *args, gpointer udata)
+{
+	if (publish_delayed) {
+		scr_log_print (LPRINT_DEBUG, "tune: Publishing delayed data.");
+
+		publish_delayed = FALSE;
+		tune_publish_info ();
+	}
+
+	return HOOK_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+}
+
+static guint tune_hdh (const gchar *htype, hk_arg_t *args, gpointer udata)
+{
+#ifdef HAVE_LM_CONNECTION_UNREGISTER_REPLY_HANDLER
+	if (lconnection && tune_reply_handler)
+		lm_connection_unregister_reply_handler (lconnection, tune_reply_handler);
+#endif
+
+	return HOOK_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+}
+
+static guint tune_htoh ( const gchar *htype, hk_arg_t *args, gpointer udata )
+{
+	tune_publish ( (const tune_pair_t *) args );
+	return HOOK_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+}
+
+static gchar *tune_guard (const char *key, const char *new_value)
+{
+	if (new_value)
+		tune_interval = atoi (new_value);
+	else
+		tune_interval = 0;
+
+	if (tune_source) {
+		g_source_remove (tune_source);
+		tune_source = 0;
+		// this will reinstall source with proper timeout, if necessary
+		tune_publish_info ();
+	}
+
+	return g_strdup (new_value);
+}
+
+void pep_tune_init(void)
+{
+	tune_gerror_quark = g_quark_from_string ( "pep-tune-gerror-quark" );
+
+	tune_interval = settings_opt_get_int ("tune_interval");
+
+	tune_guard_installed = settings_set_guard ("tune_interval", tune_guard);
+	if (!tune_guard_installed)
+		scr_log_print (LPRINT_LOGNORM, "tune: Warning: cannot install option guard for 'tune_interval'");
+
+	pep_register_xmlns_handler (NS_TUNE, tune_handler, NULL, NULL);
+
+	tune_reply_handler = lm_message_handler_new ( tune_publish_reply_handler, NULL, NULL );
+
+	tune_hid_connect    = hk_add_handler ( tune_hch,  HOOK_POST_CONNECT, G_PRIORITY_DEFAULT, NULL );
+	tune_hid_disconnect = hk_add_handler ( tune_hdh,  HOOK_PRE_DISCONNECT, G_PRIORITY_DEFAULT, NULL );
+	tune_hid_tuneout    = hk_add_handler ( tune_htoh, HOOK_TUNE_OUT, G_PRIORITY_DEFAULT, NULL );
+
+	xmpp_add_feature ( NS_TUNE        );
+	xmpp_add_feature ( NS_TUNE_NOTIFY );
+}
+
+void pep_tune_uninit ( void )
+{
+	xmpp_del_feature ( NS_TUNE        );
+	xmpp_del_feature ( NS_TUNE_NOTIFY );
+
+	hk_del_handler ( HOOK_POST_CONNECT,   tune_hid_connect );
+	hk_del_handler ( HOOK_PRE_DISCONNECT, tune_hid_disconnect );
+	hk_del_handler ( HOOK_TUNE_OUT,       tune_hid_tuneout );
+
+	if ( tune_source )
+		g_source_remove ( tune_source );
+
+	pep_unregister_xmlns_handler ( NS_TUNE );
+
+	if ( tune_reply_handler ) {
+#ifdef HAVE_LM_CONNECTION_UNREGISTER_REPLY_HANDLER
+		if ( lconnection )
+			lm_connection_unregister_reply_handler ( lconnection, tune_reply_handler  );
+#endif
+
+		lm_message_handler_invalidate ( tune_reply_handler );
+		lm_message_handler_unref ( tune_reply_handler );
+	}
+
+	{
+		int i;
+		for ( i = 0; i <= MAX_NO; i ++ )
+			g_free ( info[i].value );
+	}
+
+	if ( tune_guard_installed )
+		settings_del_guard ( "tune_interval" );
+}
+
+/* vim: se ts=4 sw=4: */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tune.c	Sun May 20 22:15:51 2012 +0300
@@ -0,0 +1,302 @@
+
+/* Copyright 2009-2012 Myhailo Danylenko
+ *
+ * This file is part of mcabber-pep
+ *
+ * mcabber-pep 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, see <http://www.gnu.org/licenses/>. */
+
+#include <glib.h>
+#include <string.h>
+
+#include <mcabber/commands.h>
+#include <mcabber/compl.h>
+#include <mcabber/utils.h>
+#include <mcabber/screen.h>
+#include <mcabber/logprint.h>
+#include <mcabber/roster.h>
+#include <mcabber/hbuf.h>      // HBUF_PREFIX_*
+#include <mcabber/hooks.h>
+#include <mcabber/modules.h>
+
+#include "tune.h"
+
+#include "config.h"
+
+//
+//  module description
+//
+
+void tune_init   (void);
+void tune_uninit (void);
+
+#define DESCRIPTION ( \
+	"User interface for PEP tune events\n" \
+	"Prints incoming notifications into buddies buffers\n" \
+	"Provides command /tune" )
+
+static const gchar *deps[] = { "pep_tune", NULL };
+
+static module_info_t info_tune_dev = {
+	.branch      = "dev",
+	.api         = 20,
+	.version     = PROJECT_VERSION,
+	.description = DESCRIPTION,
+	.requires    = deps,
+	.init        = tune_init,
+	.uninit      = tune_uninit,
+	.next        = NULL,
+};
+
+static module_info_t info_tune_0_10_0 = {
+	.branch      = "0.10.0",
+	.api         = 1,
+	.version     = PROJECT_VERSION,
+	.description = DESCRIPTION,
+	.requires    = deps,
+	.init        = tune_init,
+	.uninit      = tune_uninit,
+	.next        = &info_tune_dev,
+};
+
+module_info_t info_tune = {
+	.branch      = "0.10.1",
+	.api         = 1,
+	.version     = PROJECT_VERSION,
+	.description = DESCRIPTION,
+	.requires    = deps,
+	.init        = tune_init,
+	.uninit      = tune_uninit,
+	.next        = &info_tune_0_10_0,
+};
+
+//
+//  globals
+//
+
+#ifdef MCABBER_API_HAVE_CMD_ID
+static gpointer tune_cmid     = NULL;
+static gboolean tune_set_safe = FALSE;
+#endif
+
+static guint tune_cid        = 0;
+static guint tune_hid_tunein = 0;
+
+//
+//  code
+//
+
+static void do_tune (char *arg)
+{
+	hk_arg_t tags[] = {
+		{ "artist", NULL },
+		{ "length", NULL },
+		{ "rating", NULL },
+		{ "source", NULL },
+		{ "title",  NULL },
+		{ "track",  NULL },
+		{ "uri",    NULL },
+		{ NULL,     NULL },
+	};
+
+	if (!*arg) { // request
+
+		GError *error = NULL;
+
+		tune_request ( CURRENT_JID, &error );
+		if ( error ) {
+			scr_log_print ( LPRINT_NORMAL, "Error sending request: %s.", error -> message );
+			g_error_free ( error );
+		} else
+			scr_log_print ( LPRINT_NORMAL, "Request sent." ); // XXX
+
+		return;
+
+	} else if (arg[0] != '-' || arg[1] != '\0') { 
+
+		char *p;
+		char *argstart     = NULL;
+		char *argstop      = NULL;
+		char *tagstart     = NULL;
+		char *tagstop      = NULL;
+		char *wordstart    = arg;
+		gboolean proceed   = TRUE;
+
+// pt = p, ws = wordstart, ts = tagstart, tt = tagstop, as = argstart, at = argstop
+// tag=value value tag=value
+// w  p
+// s  t
+// t  ta
+// s  t*
+// tag=value value tag=value
+// t  ta    p
+// s  t*    t
+// t  ta    aw
+// s  ts    ts
+// tag=value value tag=value
+// t  ta    aw    p
+// s  ts    ts    t
+// t  ta          aw
+// s  ts          ts
+// tag=value value tag=value
+// t  ta          aw  p
+// s  ts          ts  t
+//                 t  ta
+//                 s  t*
+// tag=value value tag=value
+//                 t  ta    p
+//                 s  t*    t
+//                 t  ta    a
+//                 s  ts    t
+
+		for (p = arg; proceed; ++p) {
+			switch (*p) {
+			case '=':
+				if (tagstart && tagstop - tagstart) {
+					// process previous args
+					hk_arg_t *tag;
+
+					for (tag = tags; tag->name; ++tag) {
+						if (!strncmp (tag->name, tagstart, tagstop - tagstart)) {
+							g_free ( (gchar *) tag->value );
+							if (argstop - argstart) {
+								*argstop = '\0';
+								tag->value = to_utf8 (argstart);
+							}
+							break;
+						}
+					}
+				}
+
+				tagstart = wordstart;
+				tagstop  = p;
+				argstop  = p+1;
+				argstart = p+1;
+				break;
+
+			case '\0':
+				argstop = p;
+
+				if (tagstop - tagstart) {
+					// process previous args
+					hk_arg_t *tag;
+
+					for (tag = tags; tag->name; ++tag) {
+						if (!strncmp (tag->name, tagstart, tagstop - tagstart)) {
+							g_free ( (gchar *) tag->value );
+							if (argstop - argstart)
+								tag->value = to_utf8 (argstart);
+							break;
+						}
+					}
+				}
+
+				proceed = FALSE;
+				break;
+
+			case ' ':
+				argstop = p;
+				wordstart = p+1;
+				break;
+
+			default:
+				break;
+			}
+		}
+	}
+
+	hk_run_handlers ( HOOK_TUNE_OUT, tags );
+
+	{
+		hk_arg_t *tag;
+
+		for (tag = tags; tag->name; ++tag)
+			g_free ( (gchar *) tag->value );
+	}
+}
+
+static guint tune_htih ( const gchar *htype, hk_arg_t *args, gpointer udata )
+{
+	GString     *mesg = g_string_new ( NULL );
+	const gchar *from = NULL;
+
+	{
+		hk_arg_t *arg;
+		for ( arg = args; arg -> name; arg ++ )
+			if ( arg -> value ) {
+				if ( ! strcmp ( arg -> name, "from" ) )
+					from = arg -> value;
+				else
+					g_string_append_printf ( mesg, "\n - %s: %s", arg -> name, arg -> value );
+			}
+	}
+
+	if (mesg->len)
+		g_string_prepend (mesg, "Now listens to:");
+	else
+		g_string_overwrite (mesg, 0, "Now listening to nothing");
+
+	{ // print to buddy's buffer
+		gchar *jid = jidtodisp ( from );
+
+		scr_write_incoming_message ( jid, mesg->str, 0, HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0 ); // NO conversion from utf-8
+
+		g_free ( jid );
+	}
+
+	g_string_free ( mesg, TRUE );
+
+	return HOOK_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+}
+
+void tune_init(void)
+{
+	tune_cid = compl_new_category ();
+	if (tune_cid) {
+		compl_add_category_word (tune_cid, "artist=");
+		compl_add_category_word (tune_cid, "length=");
+		compl_add_category_word (tune_cid, "rating=");
+		compl_add_category_word (tune_cid, "source=");
+		compl_add_category_word (tune_cid, "title=");
+		compl_add_category_word (tune_cid, "track=");
+		compl_add_category_word (tune_cid, "uri=");
+	}
+
+#ifndef MCABBER_API_HAVE_CMD_ID
+	cmd_add ("tune", "", tune_cid, tune_cid, do_tune, NULL);
+#else
+	tune_cmid     = cmd_add ("tune", "", tune_cid, tune_cid, do_tune, NULL);
+	tune_set_safe = cmd_set_safe ("tune", TRUE);
+#endif
+
+	tune_hid_tunein = hk_add_handler (tune_htih, HOOK_TUNE_IN, G_PRIORITY_DEFAULT, NULL);
+}
+
+void tune_uninit(void)
+{
+	hk_del_handler (HOOK_TUNE_IN, tune_hid_tunein);
+
+#ifndef MCABBER_API_HAVE_CMD_ID
+	cmd_del ("tune");
+#else
+	if (tune_cmid)
+		cmd_del (tune_cmid);
+	if (tune_set_safe)
+		cmd_set_safe ("tune", FALSE);
+#endif
+
+	if (tune_cid)
+		compl_del_category (tune_cid);
+}
+
+/* vim: se ts=4 sw=4: */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tune.h	Sun May 20 22:15:51 2012 +0300
@@ -0,0 +1,45 @@
+
+#ifndef MCABBER_TUNE_H
+#define MCABBER_TUNE_H
+
+/*
+ * tune.h               -- Pep tune events
+ *
+ * Copyright (C) 2009-2012 Myhailo Danylenko <isbear@ukrpost.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <glib.h>
+
+#define NS_TUNE        ( "http:/" "/jabber.org/protocol/tune"        )
+#define NS_TUNE_NOTIFY ( "http:/" "/jabber.org/protocol/tune+notify" )
+
+#define HOOK_TUNE_IN  ( "tune-in"  )
+#define HOOK_TUNE_OUT ( "tune-out" )
+
+#define TUNE_ERROR_NOTCONNECTED ( 0x01 )
+
+typedef struct {
+	const gchar *name;
+	gchar       *value;
+} tune_pair_t;
+
+void tune_publish ( const tune_pair_t *pairs );
+gboolean tune_request ( const gchar *to, GError **err );
+
+#endif
+