/* GDM - The Gnome Display Manager
 * Copyright (C) 1999, 2000 Martin K. Petersen <mkp@mkp.net>
 *
 * This file Copyright (c) 2003 Sun Microsystems Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <syslog.h>

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>

#include <glib.h>
#include <gmodule.h>
#include <gdk/gdk.h>
#include <gdk/gdkx.h>
#include <gtk/gtk.h>
#include <glib/gi18n.h>

#ifdef HAVE_XINPUT
#include <X11/extensions/XInput.h>
#endif

/*
 * Note that CONFIGFILE will have to be moved to somewhere more generic
 * if this module is ever moved outside of gdm.
 */
#define CONFIGFILE GDMCONFDIR "/modules/AccessKeyMouseEvents"
#define	iseol(ch)	((ch) == '\r' || (ch) == '\f' || (ch) == '\0' || \
			(ch) == '\n')

#define N_INPUT_TYPES 40

typedef enum
{
	GESTURE_TYPE_KEY	= 1 << 0,
	GESTURE_TYPE_MOUSE	= 1 << 1,
	GESTURE_TYPE_BUTTON	= 1 << 2
} GestureType;

typedef enum {
  XINPUT_TYPE_MOTION = 0,
  XINPUT_TYPE_BUTTON_PRESS   = 1,
  XINPUT_TYPE_BUTTON_RELEASE = 2,
  XINPUT_TYPE_KEY_PRESS      = 3,
  XINPUT_TYPE_KEY_RELEASE    = 4
} XInputEventType;

typedef struct {
	guint keysym;
	GdkModifierType state;
	guint keycode;
} Key;

typedef struct {
	guint number;
	GdkModifierType state;
} Button;

union Input {
	Key key;
	Button button;
};

typedef struct {
	GestureType type;
	union Input input;
	char *gesture_str;
	GSList *actions;
	guint n_times;
	guint duration;
	guint timeout;
	gint  start_time;
	gint  seq_count;
} Gesture;

static int xinput_types[N_INPUT_TYPES];

extern char **environ;

static gboolean debug_gestures = FALSE;
static GSList   *gesture_list  = NULL;
static int      lineno         = 0;

static gchar * screen_exec_display_string (GdkScreen *screen, const char *old);
static void create_event_watcher (void);
static void load_gestures(gchar *path);
static gchar ** get_exec_environment (XEvent *xevent);
static Gesture * parse_line(gchar *buf);
static GdkFilterReturn gestures_filter (GdkXEvent *gdk_xevent, GdkEvent *event, gpointer data);
static gint is_mouseX (const gchar *string);
static gint is_switchX (const gchar *string);

#define gesture_list_get_matches(a, b, c) (g_slist_find_custom (a, b, c))

static void
free_gesture (Gesture *gesture)
{
	if (gesture == NULL)
		return;

	g_slist_foreach (gesture->actions, (GFunc)g_free, NULL);
	g_slist_free (gesture->actions);
	g_free (gesture->gesture_str);
	g_free (gesture);
}
			
static gchar *
screen_exec_display_string (GdkScreen *screen, const char *old)
{
	GString     *str;
	const gchar *old_display;
	gchar       *retval;
	gchar       *p;
  
	g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL);

	old_display = gdk_display_get_name (gdk_screen_get_display (screen));

	str = g_string_new ("DISPLAY=");
	g_string_append (str, old_display);

	p = strrchr (str->str, '.');
	if (p && p >  strchr (str->str, ':'))
		g_string_truncate (str, p - str->str);

	g_string_append_printf (str, ".%d", gdk_screen_get_number (screen));

	retval = str->str;

	g_string_free (str, FALSE);

	return retval;
}

static void
init_xinput (GdkDisplay *display, GdkWindow *root)
{
#ifdef HAVE_XINPUT
	XEventClass  event_list[40];
	int          i, j, number = 0, num_devices; 
	XDeviceInfo  *devices = NULL;
	XDevice      *device = NULL;

	devices = XListInputDevices (GDK_DISPLAY_XDISPLAY (display),
		&num_devices);

	if (debug_gestures)
	    syslog (LOG_WARNING, "checking %d input devices...", num_devices);

	for (i=0; i < num_devices; i++) {
		if (devices[i].use == IsXExtensionDevice)
		{
			device = XOpenDevice (GDK_DISPLAY_XDISPLAY (display),
				devices[i].id);

			for (j=0; j < device->num_classes && number < 39; j++) {

				switch (device->classes[j].input_class) 
				{
				case KeyClass:
					DeviceKeyPress (device, 
					    xinput_types[XINPUT_TYPE_KEY_PRESS], 
					    event_list[number]);
					number++;
					DeviceKeyRelease (device, 
					    xinput_types[XINPUT_TYPE_KEY_RELEASE], 
					    event_list[number]);
					number++;
					break;      
				case ButtonClass:
					DeviceButtonPress (device, 
					    xinput_types[XINPUT_TYPE_BUTTON_PRESS], 
					    event_list[number]);
					number++;
					DeviceButtonRelease (device, 
					    xinput_types[XINPUT_TYPE_BUTTON_RELEASE], 
					    event_list[number]);
					number++;
					break;
				case ValuatorClass:
					DeviceMotionNotify (device, 
					    xinput_types[XINPUT_TYPE_MOTION], 
					    event_list[number]);
					number++;
				}
			}
		}
	}

	if (debug_gestures)
	    syslog (LOG_WARNING, "%d event types available", number);

	if (XSelectExtensionEvent(GDK_WINDOW_XDISPLAY (root), 
				  GDK_WINDOW_XWINDOW (root),
				  event_list, number)) {
		if (debug_gestures)
			syslog (LOG_WARNING,
				"Can't select input device events!");
	}
#endif
}

static void create_event_watcher (void)
{
	GdkDisplay *display;

	display = gdk_display_get_default();
	if (!display)
		return;

	load_gestures(CONFIGFILE);

	init_xinput (display, gdk_screen_get_root_window (
		gdk_display_get_default_screen (display)));

	gdk_window_add_filter (NULL, gestures_filter, NULL);

	return;
}


static void
load_gestures(gchar *path)
{
	FILE *fp;
	Gesture *tmp_gesture;
	gchar buf[1024];

	fp = fopen (path, "r");
	if (fp == NULL) {
		/* TODO - I18n */
		if (debug_gestures) 
		syslog (LOG_WARNING, _("Cannot open gestures file: %s"),
			path);
		return;
	}

	while (fgets (buf, sizeof (buf), fp) != NULL) {

		tmp_gesture = (Gesture *)parse_line(buf);
		if (tmp_gesture) {

			/* Is the key already associated with an existing
			   gesture? */
			if (strcmp (tmp_gesture->gesture_str, "<Add>") == 0) {

				/* Add another action to the last gesture */
				Gesture *last_gesture;
				GSList *last_item = g_slist_last (gesture_list);

				/* If there is no last_item to add onto ignore
				   the entry */
				if (last_item) {
					last_gesture = (Gesture *)
						last_item->data;

					/* Add the action to the last gesture's
					   actions list */
					last_gesture->actions = 
						g_slist_append (last_gesture->actions,
						g_strdup ((gchar *)tmp_gesture->actions->data));
				}
				free_gesture (tmp_gesture);

				/* We must be able to deal with multiple
				   unambiguous gestures attached to one
				   switch/button */
			} else {
				gesture_list = g_slist_append (gesture_list,
					tmp_gesture);
			}
		}
	}
	fclose (fp);
}


/**
 * get_exec_environment:
 *
 * Description: Modifies the current program environment to
 * ensure that $DISPLAY is set such that a launched application
 * inheriting this environment would appear on screen.
 *
 * Returns: a newly-allocated %NULL-terminated array of strings or
 * %NULL on error. Use g_strfreev() to free it.
 *
 * mainly ripped from egg_screen_exec_display_string in 
 * gnome-panel/egg-screen-exec.c
 **/
static gchar **
get_exec_environment (XEvent *xevent)
{
	gchar **retval = NULL;
	gint    i;
	gint    display_index = -1;

	GdkScreen *screen = NULL;
  	GdkWindow *window = gdk_xid_table_lookup (xevent->xkey.root);

  	if (window)
		screen = gdk_drawable_get_screen (GDK_DRAWABLE (window));

	/* TODO: revisit this fallback, it's suspect since Xi events might not
	   have xkey.root set */

	else
		screen = gdk_display_get_default_screen (gdk_display_get_default ());   

	g_assert (GDK_IS_SCREEN (screen));

	for (i=0; environ [i]; i++)
		if (!strncmp (environ [i], "DISPLAY", 7))
  			display_index = i;

	if (display_index == -1)
		display_index = i++;

	retval = g_new0 (char *, i + 1);

	for (i=0; environ [i]; i++) {
 		if (i == display_index)
  			retval [i] = screen_exec_display_string (screen,
				environ[i]);
		else
			retval [i] = g_strdup (environ [i]);
	}

	retval [i] = NULL;

	return retval;
}

static Gesture *
parse_line (gchar *buf)
{
	static GdkDisplay *display = NULL;
	Gesture *tmp_gesture       = NULL;
	gchar  *keystring;
	gchar  *keyservice;
	gint    button = 0;
	
	if (!display) {
		if ((display = gdk_display_get_default()) == NULL)
			return NULL;
	}
	lineno++;

	if ((*buf == '#') || (iseol(*buf)) || (buf == NULL))
		return NULL;
	
	/* Find the key name */
	keystring = strtok (buf, " \t\n\r\f");
	if (keystring == NULL) {
		/* TODO - Error messages */
		return NULL;
	}

	tmp_gesture = g_new0 (Gesture, 1);
	tmp_gesture->gesture_str = g_strdup (keystring);

	if (strcmp (tmp_gesture->gesture_str, "<Add>") != 0) {
		guint n, duration, timeout;
		gchar *tmp_string;

		tmp_gesture->start_time = 0;
		tmp_gesture->seq_count = 0;

		button = is_mouseX (tmp_gesture->gesture_str);
		if (button > 0) {
			tmp_gesture->type = GESTURE_TYPE_MOUSE;
			tmp_gesture->input.button.number = button;
		} else if ((button = is_switchX (tmp_gesture->gesture_str)) == TRUE) {
			tmp_gesture->type = GESTURE_TYPE_BUTTON;
			tmp_gesture->input.button.number = button;
		} else {
			tmp_gesture->type = GESTURE_TYPE_KEY;
			gtk_accelerator_parse (tmp_gesture->gesture_str, 
	 			&(tmp_gesture->input.key.keysym), 
	 			&(tmp_gesture->input.key.state));
			if (tmp_gesture->input.key.keysym == 0 &&
			    tmp_gesture->input.key.state == 0) {
				/* TODO - Error messages here */
				free_gesture (tmp_gesture);
				return NULL;
			}
			tmp_gesture->input.key.keycode = 
				XKeysymToKeycode (GDK_DISPLAY_XDISPLAY (display),
				tmp_gesture->input.key.keysym);
		}

		if (tmp_gesture->type == 0) {
			/* TODO - Error messages here */
			free_gesture (tmp_gesture);
			return NULL;
		}
		/* [TODO] Need to clean up here. */
		 
		/* Find the repetition number */
		tmp_string = strtok (NULL, " \t\n\r\f");
		if (tmp_string == NULL) {
			/* TODO - Error messages */
			free_gesture (tmp_gesture);
			return NULL;
		}

		/* TODO - the above doesn't check for the string to
		   be all digits */
		if ((n=atoi (tmp_string)) <= 0) {
			/* Add an error message */
			free_gesture (tmp_gesture);
			return NULL;
		}
		tmp_gesture->n_times = n;

		/*
		 * Find the key press duration (in ms)
		 */
		tmp_string = strtok (NULL, " \t\n\r\f");
		if (tmp_string == NULL) {
			/* TODO - Error messages */
			free_gesture (tmp_gesture);
			return NULL;
		}
		/* TODO - the above doesn't check for the string to
		   be all digits */

		duration = atoi (tmp_string);
		if (duration < 0) {
			/* Add an error message */
			free_gesture (tmp_gesture);
			return NULL;
		}
		tmp_gesture->duration = duration;

		/*
		 * Find the timeout duration (in ms). Timeout value is the 
		 * time within which consecutive keypress actions must be
		 * performed by the user before the sequence is discarded.
		 */
		tmp_string = strtok (NULL, " \t\n\r\f");
		if (tmp_string == NULL) {
			/* TODO - Error messages */
			free_gesture (tmp_gesture);
			return NULL;
		}

		/*
		 * A gesture with an n_times value greater than 1 and a
		 * non-positive timeout can never be triggered, so do not
		 * accept such gestures.  The value of timeout is not used
		 * if n_times is 1, so don't bother setting the timeout in
		 * this case.
		 */
		tmp_gesture->timeout = 0;
		if (tmp_gesture->n_times > 1) {
			if ((timeout=atoi (tmp_string)) <= 0) {
				/* Add an error message */;
				free_gesture (tmp_gesture);
				return NULL;
			}
			tmp_gesture->timeout = timeout;
		}
	}

	/*
	 * Find servcice. Permit blank space so arguments can be supplied.
	 */
	keyservice = strtok (NULL, "\n\r\f");
	if (keyservice == NULL) {
		/* TODO - Error messages */
		free_gesture (tmp_gesture);
		return NULL;
	}
	/* skip over initial whitespace */
	while (*keyservice && isspace (*keyservice))
		keyservice++;

	tmp_gesture->actions = g_slist_append(tmp_gesture->actions,
		g_strdup(keyservice));

	if (debug_gestures)
	    syslog (LOG_WARNING, "gesture parsed for %s button %d", 
		   (tmp_gesture->type == GESTURE_TYPE_MOUSE) ? "mouse" :
		   ((tmp_gesture->type == GESTURE_TYPE_BUTTON) ? "switch" :
		   "key"), tmp_gesture->input.button.number);

	return tmp_gesture;
}

/* 
 * These modifiers are ignored because they make no sense.
 * .eg <NumLock>x 
 *
 * FIXME: [sadly, NumLock isn't always mapped to the same modifier, so the logic
 *        below is faulty - bill]
 */
#define IGNORED_MODS (GDK_LOCK_MASK | GDK_MOD2_MASK | GDK_MOD3_MASK)
#define USED_MODS (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK | \
	   GDK_MOD4_MASK | GDK_MOD5_MASK)

static gboolean
change_cursor_back (gpointer data)
{
	GdkCursor *cursor = gdk_cursor_new (GDK_LEFT_PTR);
	gdk_window_set_cursor (gdk_get_default_root_window (), cursor);
	gdk_cursor_unref (cursor);

	return FALSE;
}

static gint
event_time (XEvent *ev)
{
	if ((ev->type == KeyPress) ||
	    (ev->type == KeyRelease))
		return ((XKeyEvent *) ev)->time;
	else if ((ev->type == ButtonPress) ||
		 (ev->type == ButtonRelease))
		return ((XButtonEvent *) ev)->time;
	else if ((ev->type == xinput_types[XINPUT_TYPE_KEY_PRESS]) ||
		 (ev->type == xinput_types[XINPUT_TYPE_KEY_RELEASE]))
		return ((XDeviceKeyEvent *) ev)->time;
	else if ((ev->type == xinput_types[XINPUT_TYPE_BUTTON_PRESS]) ||
		 (ev->type == xinput_types[XINPUT_TYPE_BUTTON_RELEASE]))
		return ((XDeviceButtonEvent *) ev)->time;
	else
		return 0;
}

static gint
elapsed_time (XEvent *ev1, XEvent *ev2)
{
	return event_time (ev2) - event_time (ev1);
}

static gboolean
keycodes_equal (XEvent *ev1, XEvent *ev2)
{
	if (ev1->type == ev2->type)
	{
		if (ev1->type == KeyPress || ev1->type == KeyRelease)
		{
			return (((XKeyEvent *) ev1)->keycode == ((XKeyEvent *) ev2)->keycode);
		}
		else if (ev1->type == xinput_types[XINPUT_TYPE_KEY_PRESS] ||
			 ev1->type == xinput_types[XINPUT_TYPE_KEY_RELEASE])
		{
			return (((XDeviceKeyEvent *) ev1)->keycode == ((XDeviceKeyEvent *) ev2)->keycode);
		}
	}
	return FALSE;
}

static gint
key_gesture_compare_func (gconstpointer a, gconstpointer b)
{
	const Gesture *gesture = a;
	const XEvent  *xev     = b;

	if (gesture->type == GESTURE_TYPE_KEY) 
	{
	    if (((xev->type == KeyPress) || (xev->type == KeyRelease)) &&
		(xev->xkey.keycode == gesture->input.key.keycode) &&
		((xev->xkey.state & USED_MODS) == gesture->input.key.state))
		return 0;
	    else if (((xev->type == xinput_types[XINPUT_TYPE_KEY_PRESS]) ||
		     (xev->type == xinput_types[XINPUT_TYPE_KEY_RELEASE])) &&
		     (xev->xkey.keycode == gesture->input.key.keycode) &&
		     ((xev->xkey.state & USED_MODS) == gesture->input.key.state))
		return 0;
	    else
		return 1;
	}
	else if ((gesture->type == GESTURE_TYPE_MOUSE) &&
		 ((xev->type == ButtonPress) || (xev->type == ButtonRelease)) &&
		 (xev->xbutton.button == gesture->input.button.number))
		return 0;
	else if ((gesture->type == GESTURE_TYPE_BUTTON) &&
		 ((xev->type == xinput_types[XINPUT_TYPE_BUTTON_PRESS]) ||
		  (xev->type == xinput_types[XINPUT_TYPE_BUTTON_RELEASE])) &&
		 ((XDeviceButtonEvent *) xev)->button == gesture->input.button.number)
		return 0;
	else
		return 1;
}

#define event_is_gesture_type(xevent) (xevent->type == KeyPress ||\
	    xevent->type == KeyRelease ||\
	    xevent->type == ButtonPress ||\
	    xevent->type == ButtonRelease ||\
	    xevent->type == xinput_types[XINPUT_TYPE_KEY_PRESS] ||\
	    xevent->type == xinput_types[XINPUT_TYPE_KEY_RELEASE] ||\
	    xevent->type == xinput_types[XINPUT_TYPE_BUTTON_PRESS] ||\
	    xevent->type == xinput_types[XINPUT_TYPE_BUTTON_RELEASE])

static GdkFilterReturn
gestures_filter (GdkXEvent *gdk_xevent,
		 GdkEvent *event,
		 gpointer data)
{
	XEvent  *xevent = (XEvent *)gdk_xevent;
	GSList  *li, *act_li;
	Gesture *curr_gesture = NULL;
	XID xinput_device = None;
	
	static XEvent *last_event = NULL;
	static gint seq_count = 0;

	if (!event_is_gesture_type (xevent)) 
		return GDK_FILTER_CONTINUE;

	if (!last_event)
		last_event = g_new0(XEvent, 1);

	if ((xevent->type == KeyPress) ||
	    (xevent->type == xinput_types[XINPUT_TYPE_KEY_PRESS]))
	{

		if (debug_gestures)
			syslog (LOG_WARNING, "key press");

		if (last_event->type == KeyPress &&
		    last_event->xkey.keycode == xevent->xkey.keycode) {
			/* these come from auto key-repeat */

			if (debug_gestures)
			    syslog (LOG_WARNING, "rejecting repeat");

			return GDK_FILTER_CONTINUE;
		}

		if (seq_count > 0 &&
		    last_event->type != KeyRelease) {

		    if (debug_gestures)
			syslog (LOG_WARNING,
			   "last event wasn't a release, resetting seq");
			seq_count = 0;
		}
		else if (seq_count > 0 &&
		    keycodes_equal (last_event, xevent)) {

			if (debug_gestures)
			    syslog (LOG_WARNING,
				"keycode doesn't match last event, resetting seq");
			seq_count = 0;
		}

		/* Find the associated gesture for this keycode & state */
		li = gesture_list_get_matches (gesture_list, xevent,
			key_gesture_compare_func);

		if (li) {
			curr_gesture = li->data;
			if (debug_gestures)
			    syslog (LOG_WARNING,
				"found a press match [%s]",
				curr_gesture->gesture_str);

			/* xevent time values are in milliseconds. */
			/* The config file spec is in ms           */
			if (curr_gesture->timeout > 0 && seq_count > 0 && 
			    elapsed_time (last_event, xevent) >
			    curr_gesture->timeout) {

				if (debug_gestures)
				    syslog (LOG_WARNING,
					 "timeout exceeded: reset seq and gesture");

 				/* The timeout has been exceeded.
				   Reset the sequence. */
				seq_count = 0;
				curr_gesture = NULL;
			}
		}
	}
	else if ((xevent->type == KeyRelease) ||
		 (xevent->type == xinput_types[XINPUT_TYPE_KEY_RELEASE]))
	{
		if (debug_gestures)
		    syslog (LOG_WARNING, "key release");

		if (seq_count > 0 &&
		    ((last_event->type != KeyPress &&
		      last_event->type != xinput_types[XINPUT_TYPE_KEY_PRESS]) ||
		      ! keycodes_equal (last_event, xevent))) {

			if (debug_gestures)
			    syslog (LOG_WARNING,
				"either last event not a keypress, or keycodes don't match. Resetting seq.");

			seq_count = 0;
		}
		
		/*
		 * Find the associated gesture for this keycode & state
		 *
		 * Note that here we check the state against the last_event,
		 * otherwise key gestures based on modifier keys such as
		 * Control_R won't work.
		 */
		li = gesture_list_get_matches (gesture_list, xevent,
			key_gesture_compare_func);

	        if (li) {
			curr_gesture = li->data;

			if (debug_gestures)
		 	   syslog (LOG_WARNING, "found a release match [%s]",
				 curr_gesture->gesture_str);

			if ((curr_gesture->duration > 0) &&
			    (elapsed_time (last_event, xevent) < curr_gesture->duration)) {

				seq_count = 0;
				curr_gesture = NULL;

				if (debug_gestures)
				    syslog (LOG_WARNING, "setting current gesture to NULL");
			} else {
				seq_count++;

				if (debug_gestures)
				    syslog (LOG_WARNING, "incrementing seq_count");
			}	
		}
	}
	else if ((xevent->type == ButtonPress) ||
		 (xevent->type == xinput_types[XINPUT_TYPE_BUTTON_PRESS])) {
		gint button = 0;
		gint time = 0;

		if (xevent->type == ButtonPress) {
			button = xevent->xbutton.button;
			time = xevent->xbutton.time;

			if (debug_gestures)
				syslog (LOG_WARNING, "button press: %d", button);

			if (seq_count > 0 && (last_event->type != ButtonRelease))
				seq_count = 0;
			else if (seq_count > 0 && last_event->xbutton.button != button) {
				seq_count = 0;
			}
		}
#ifdef HAVE_XINPUT
		else {
			button = ((XDeviceButtonEvent *) xevent)->button;
			time =  ((XDeviceButtonEvent *) xevent)->time;
			if (debug_gestures)
				syslog (LOG_WARNING, "xinput button press: %d", button);
			if (seq_count > 0 &&
			    last_event->type != xinput_types[XINPUT_TYPE_BUTTON_RELEASE]) {
				seq_count = 0;
			}
			else if (seq_count > 0 &&
				((XDeviceButtonEvent *) last_event)->button != button) {
				seq_count = 0;
			}
		}
#endif
		
		/*
		 * Find the associated gesture for this button.
		 */
		li = gesture_list_get_matches (gesture_list, xevent,
			key_gesture_compare_func);
		if (li) {
			if (debug_gestures)
				syslog (LOG_WARNING, "found match for press");

			curr_gesture = li->data;

			if (curr_gesture->timeout > 0 && seq_count > 0) {

				/* xevent time values are in milliseconds. */
				/* The config file spec is in ms           */
				if (elapsed_time (last_event, xevent) > curr_gesture->timeout) {

 					/* Timeout has elapsed. Reset the sequence. */
					seq_count    = 0;
					curr_gesture = NULL;

					if (debug_gestures)
					    syslog (LOG_WARNING, "gesture timed out.");
				}
			}
		}
		else if (debug_gestures)
			 syslog (LOG_WARNING, "no match for press %d", button);
	}
	else if ((xevent->type == ButtonRelease) ||
		 (xevent->type == xinput_types[XINPUT_TYPE_BUTTON_RELEASE]))
	{
		gint button = 0;
		gint time = 0;
		
		if (xevent->type == ButtonRelease) 
		{
			button = xevent->xbutton.button;
			time = xevent->xbutton.time;
			if (seq_count > 0 &&
			    (last_event->type != ButtonPress ||
			     last_event->xbutton.button != button)) {
				if (debug_gestures)
				    syslog (LOG_WARNING,
					"resetting count to zero, based on failure to match last event.");
				seq_count = 0;
			}
		}
#ifdef HAVE_XINPUT
		else
		{
			button        = ((XDeviceButtonEvent *)(xevent))->button;
			time          = ((XDeviceButtonEvent *)(xevent))->time;
			xinput_device = ((XDeviceButtonEvent *)(xevent))->deviceid;

			if (seq_count > 0 &&
			    (last_event->type != xinput_types[XINPUT_TYPE_BUTTON_PRESS] ||
			     ((XDeviceButtonEvent *) last_event)->button != button)) {

				if (debug_gestures)
				    syslog (LOG_WARNING,
					 "resetting count to zero, based on failure to match last input event.");

				seq_count = 0;
			}
		}
#endif

		li = gesture_list_get_matches (gesture_list, xevent,
			key_gesture_compare_func);

		if (li) {
			if (debug_gestures)
			    syslog (LOG_WARNING, "found match for release");
			curr_gesture = li->data;
			if ((curr_gesture->duration > 0) &&
			    (elapsed_time (last_event, xevent) < curr_gesture->duration)) {
				seq_count = 0;
				curr_gesture = NULL;
				if (debug_gestures)
				    syslog (LOG_WARNING, "insufficient duration.");
			} else {
				if (debug_gestures)
				    syslog (LOG_WARNING, "duration OK");
				seq_count++;
			}
		}
		else if (debug_gestures)
			syslog (LOG_WARNING, "no match for release - button %d",
				button);
	}

	/*
	 * Did this event complete any gesture sequences?
	 */
	last_event = memcpy (last_event, xevent, sizeof(XEvent));
	if (curr_gesture) {
		if (seq_count != curr_gesture->n_times) {

			if (debug_gestures)
			    syslog (LOG_WARNING,
				 "waiting for %d more repetitions...",
				 curr_gesture->n_times - seq_count);

			return GDK_FILTER_CONTINUE;
		} else {
			gboolean retval;
			gchar **argv = NULL;
			gchar **envp = NULL; 

			if (debug_gestures)
				syslog (LOG_WARNING, "gesture complete!");

			seq_count = 0;
			for (act_li = curr_gesture->actions;
			     act_li != NULL; act_li = act_li->next) {

				gchar *action = (gchar *)act_li->data;
				g_return_val_if_fail (action != NULL, GDK_FILTER_CONTINUE);
				if (!g_shell_parse_argv (action, NULL, &argv, NULL))
					continue;

				envp = get_exec_environment (xevent);

				if (debug_gestures)
				    syslog (LOG_WARNING, "Action is %s", action);

				retval = g_spawn_async (NULL,
							argv,
							envp,
							G_SPAWN_SEARCH_PATH,
							NULL,
							NULL,
							NULL,
							NULL);

				g_strfreev (argv);
				g_strfreev (envp); 

				if ( ! retval) {
					GtkWidget *dialog = 
						gtk_message_dialog_new (NULL, 0,
							GTK_MESSAGE_ERROR,
							GTK_BUTTONS_OK,
							_("Error while trying to run (%s)\n"\
							"which is linked to (%s)"),
							action,
							curr_gesture->gesture_str);
					gtk_dialog_set_has_separator (GTK_DIALOG (dialog),
								      FALSE);
					g_signal_connect (dialog, "response",
						G_CALLBACK (gtk_widget_destroy),
						NULL);
					gtk_widget_show (dialog);
				} else {
					GdkCursor *cursor = gdk_cursor_new (GDK_WATCH);
					gdk_window_set_cursor (gdk_get_default_root_window (),
						cursor);
					gdk_cursor_unref (cursor);
					g_timeout_add (2000,
						       change_cursor_back,
						       NULL);
				}
			}
   			return GDK_FILTER_CONTINUE;
		}
	}
	return GDK_FILTER_CONTINUE;
}

			
static gint
is_mouseX (const gchar *string)
{
	if ((string[0] == '<') &&
	  (string[1] == 'm' || string[1] == 'M') &&
	  (string[2] == 'o' || string[2] == 'O') &&
	  (string[3] == 'u' || string[3] == 'U') &&
	  (string[4] == 's' || string[4] == 'S') &&
	  (string[5] == 'e' || string[5] == 'E') &&
	  (isdigit(string[6]) && 
	  	(atoi(&string[6]) > 0) && 
		(atoi(&string[6]) < 6)) &&
	  (string[7] == '>'))
		return atoi(&string[6]);
	else
		return 0;
}

static gint
is_switchX (const gchar *string)
{
	if ((string[0] == '<') &&
	  (string[1] == 's' || string[1] == 'S') &&
	  (string[2] == 'w' || string[2] == 'W') &&
	  (string[3] == 'i' || string[3] == 'I') &&
	  (string[4] == 't' || string[4] == 'T') &&
	  (string[5] == 'c' || string[5] == 'C') &&
	  (string[6] == 'h' || string[6] == 'H') &&
	  (isdigit(string[7]) && 
	  	(atoi(&string[7]) > 0) && 
		(atoi(&string[7]) < 6)) &&
	  (string[8] == '>'))
		return atoi(&string[7]);
	else
		return 0;
}

/* The init function for this gtk module */
G_MODULE_EXPORT void gtk_module_init(int *argc, char* argv[]);

void gtk_module_init(int *argc, char* argv[])
{
    if (g_getenv ("GDM_DEBUG_GESTURES") != NULL)
	debug_gestures = TRUE;

    if (debug_gestures) {
	/* If not running under GDM, then need to openlog ourselves */
	if (g_getenv ("RUNNING_UNDER_GDM") == NULL)
		openlog ("gesturelistener", LOG_PID, LOG_DAEMON);

	syslog (LOG_WARNING, "keymouselistener loaded.");
    }

    create_event_watcher ();
}
